From dac98ef60a0f6db72f18b9404536be975bfe8c4c Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Wed, 27 Sep 2023 13:46:47 -0500
Subject: [PATCH 01/22] skip failing test suite (#166190)
---
.../migrations/group3/actions/actions_test_suite.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts
index bb509799c052d0..065ce179f480c3 100644
--- a/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts
+++ b/src/core/server/integration_tests/saved_objects/migrations/group3/actions/actions_test_suite.ts
@@ -756,7 +756,8 @@ export const runActionTestSuite = ({
// Reindex doesn't return any errors on it's own, so we have to test
// together with waitForReindexTask
- describe('reindex & waitForReindexTask', () => {
+ // Failing: See https://github.com/elastic/kibana/issues/166190
+ describe.skip('reindex & waitForReindexTask', () => {
it('resolves right when reindex succeeds without reindex script', async () => {
const res = (await reindex({
client,
From f3f1eec08e4808d36a7dc4294df4f917e454c883 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien=20FOUCRET?=
Date: Wed, 27 Sep 2023 21:11:46 +0200
Subject: [PATCH 02/22] Update content indices pipeline management UI with
ELSER v2 (wording) (#167401)
---
.../pipelines/ml_inference/deploy_model.tsx | 4 ++--
.../pipelines/ml_inference/model_deployed.tsx | 2 +-
.../ml_inference/model_deployment_in_progress.tsx | 2 +-
.../pipelines/ml_inference/model_started.tsx | 10 +++++-----
.../text_expansion_callout_logic.test.ts | 14 +++++++-------
.../ml_inference/text_expansion_callout_logic.ts | 6 +++---
6 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/deploy_model.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/deploy_model.tsx
index 5431c7c41454cb..ef7dd486e5eb1e 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/deploy_model.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/deploy_model.tsx
@@ -55,7 +55,7 @@ export const DeployModel = ({
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.title',
- { defaultMessage: 'Improve your results with ELSER' }
+ { defaultMessage: 'Improve your results with ELSER v2' }
)}
@@ -73,7 +73,7 @@ export const DeployModel = ({
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployed.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployed.tsx
index fe8f0b7953c7d4..50d8ea47fb8f1f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployed.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployed.tsx
@@ -51,7 +51,7 @@ export const ModelDeployed = ({
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployedTitle',
- { defaultMessage: 'Your ELSER model has deployed but not started.' }
+ { defaultMessage: 'Your ELSER v2 model has deployed but not started.' }
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployment_in_progress.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployment_in_progress.tsx
index f9b94398332559..8804f4ec584397 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployment_in_progress.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_deployment_in_progress.tsx
@@ -28,7 +28,7 @@ export const ModelDeploymentInProgress = ({
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployingTitle',
- { defaultMessage: 'Your ELSER model is deploying.' }
+ { defaultMessage: 'Your ELSER v2 model is deploying.' }
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_started.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_started.tsx
index 3c400854a5df0a..fa5a46d438041a 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_started.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/model_started.tsx
@@ -49,20 +49,20 @@ export const ModelStarted = ({
? isCompact
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedTitleCompact',
- { defaultMessage: 'Your ELSER model is running single-threaded.' }
+ { defaultMessage: 'Your ELSER v2 model is running single-threaded.' }
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedTitle',
- { defaultMessage: 'Your ELSER model has started single-threaded.' }
+ { defaultMessage: 'Your ELSER v2 model has started single-threaded.' }
)
: isCompact
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedTitleCompact',
- { defaultMessage: 'Your ELSER model is running.' }
+ { defaultMessage: 'Your ELSER v2 model is running.' }
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedTitle',
- { defaultMessage: 'Your ELSER model has started.' }
+ { defaultMessage: 'Your ELSER v2 model has started.' }
)}
@@ -91,7 +91,7 @@ export const ModelStarted = ({
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedBody',
{
defaultMessage:
- 'Enjoy the power of ELSER in your custom Inference pipeline.',
+ 'Enjoy the power of ELSER v2 in your custom Inference pipeline.',
}
)}
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts
index e36b57d6ee1106..e39230ee2b69b7 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.test.ts
@@ -80,19 +80,19 @@ describe('TextExpansionCalloutLogic', () => {
});
it('uses the correct title and message from a create error', () => {
expect(getTextExpansionError(error, undefined, undefined)).toEqual({
- title: 'Error with ELSER deployment',
+ title: 'Error with ELSER v2 deployment',
message: error.body?.message,
});
});
it('uses the correct title and message from a fetch error', () => {
expect(getTextExpansionError(undefined, error, undefined)).toEqual({
- title: 'Error fetching ELSER model',
+ title: 'Error fetching ELSER v2 model',
message: error.body?.message,
});
});
it('uses the correct title and message from a start error', () => {
expect(getTextExpansionError(undefined, undefined, error)).toEqual({
- title: 'Error starting ELSER deployment',
+ title: 'Error starting ELSER v2 deployment',
message: error.body?.message,
});
});
@@ -303,7 +303,7 @@ describe('TextExpansionCalloutLogic', () => {
describe('textExpansionError', () => {
const error = {
body: {
- error: 'Error with ELSER deployment',
+ error: 'Error with ELSER v2 deployment',
message: 'Mocked error message',
statusCode: 500,
},
@@ -318,21 +318,21 @@ describe('TextExpansionCalloutLogic', () => {
it('returns extracted error for create', () => {
CreateTextExpansionModelApiLogic.actions.apiError(error);
expect(TextExpansionCalloutLogic.values.textExpansionError).toStrictEqual({
- title: 'Error with ELSER deployment',
+ title: 'Error with ELSER v2 deployment',
message: 'Mocked error message',
});
});
it('returns extracted error for fetch', () => {
FetchTextExpansionModelApiLogic.actions.apiError(error);
expect(TextExpansionCalloutLogic.values.textExpansionError).toStrictEqual({
- title: 'Error fetching ELSER model',
+ title: 'Error fetching ELSER v2 model',
message: 'Mocked error message',
});
});
it('returns extracted error for start', () => {
StartTextExpansionModelApiLogic.actions.apiError(error);
expect(TextExpansionCalloutLogic.values.textExpansionError).toStrictEqual({
- title: 'Error starting ELSER deployment',
+ title: 'Error starting ELSER v2 deployment',
message: 'Mocked error message',
});
});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts
index e808e8cbf74403..e8e6913c38ce87 100644
--- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/text_expansion_callout_logic.ts
@@ -97,7 +97,7 @@ export const getTextExpansionError = (
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCreateError.title',
{
- defaultMessage: 'Error with ELSER deployment',
+ defaultMessage: 'Error with ELSER v2 deployment',
}
),
message: getErrorsFromHttpResponse(createError)[0],
@@ -107,7 +107,7 @@ export const getTextExpansionError = (
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionStartError.title',
{
- defaultMessage: 'Error starting ELSER deployment',
+ defaultMessage: 'Error starting ELSER v2 deployment',
}
),
message: getErrorsFromHttpResponse(startError)[0],
@@ -117,7 +117,7 @@ export const getTextExpansionError = (
title: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionFetchError.title',
{
- defaultMessage: 'Error fetching ELSER model',
+ defaultMessage: 'Error fetching ELSER v2 model',
}
),
message: getErrorsFromHttpResponse(fetchError)[0],
From 594cb14a2811110b2edee247cd6e1276dcd095fc Mon Sep 17 00:00:00 2001
From: Jordan <51442161+JordanSh@users.noreply.github.com>
Date: Wed, 27 Sep 2023 22:35:03 +0300
Subject: [PATCH 03/22] [Cloud Security] Azure - AMR Template form (#166910)
---
.../common/constants.ts | 6 +
.../cloud_security_posture/common/types.ts | 6 +
.../common/utils/helpers.ts | 7 +
.../public/common/constants.ts | 7 +-
.../azure_credentials_form.tsx | 238 ++++++++++++++++++
.../get_azure_credentials_form_options.tsx | 51 ++++
.../azure_credentials_form/hooks.ts | 176 +++++++++++++
.../csp_boxed_radio_group.tsx | 2 +-
.../components/fleet_extensions/mocks.ts | 32 ++-
.../policy_template_form.test.tsx | 51 +++-
.../fleet_extensions/policy_template_form.tsx | 134 +++++++++-
.../policy_template_selectors.tsx | 3 +
.../components/fleet_extensions/utils.ts | 19 +-
.../post_install_azure_arm_template_modal.tsx | 103 ++++++++
.../single_page_layout/hooks/form.tsx | 15 ++
.../single_page_layout/index.tsx | 9 +
.../create_package_policy_page/types.ts | 1 +
.../azure_arm_template_instructions.tsx | 73 ++++++
.../agent_enrollment_flyout/hooks.tsx | 52 +++-
.../agent_enrollment_flyout/instructions.tsx | 1 +
.../steps/compute_steps.tsx | 10 +
.../agent_enrollment_flyout/steps/index.tsx | 1 +
..._azure_arm_template_managed_agent_step.tsx | 52 ++++
.../agent_enrollment_flyout/types.ts | 10 +
.../components/azure_arm_template_guide.tsx | 72 ++++++
.../use_create_azure_arm_template_url.ts | 51 ++++
...get_azure_arm_props_from_package_policy.ts | 32 +++
...ion_template_url_from_package_info.test.ts | 66 -----
...et_template_url_from_agent_policy.test.ts} | 59 ++++-
... => get_template_url_from_agent_policy.ts} | 19 +-
...get_template_url_from_package_info.test.ts | 112 +++++++++
... => get_template_url_from_package_info.ts} | 17 +-
x-pack/plugins/fleet/public/services/index.ts | 4 +-
.../translations/translations/fr-FR.json | 1 -
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
36 files changed, 1370 insertions(+), 124 deletions(-)
create mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx
create mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/get_azure_credentials_form_options.tsx
create mode 100644 x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/hooks.ts
create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_azure_arm_template_modal.tsx
create mode 100644 x-pack/plugins/fleet/public/components/agent_enrollment_flyout/azure_arm_template_instructions.tsx
create mode 100644 x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_azure_arm_template_managed_agent_step.tsx
create mode 100644 x-pack/plugins/fleet/public/components/azure_arm_template_guide.tsx
create mode 100644 x-pack/plugins/fleet/public/hooks/use_create_azure_arm_template_url.ts
create mode 100644 x-pack/plugins/fleet/public/services/get_azure_arm_props_from_package_policy.ts
delete mode 100644 x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.test.ts
rename x-pack/plugins/fleet/public/services/{get_cloud_formation_template_url_from_agent_policy.test.ts => get_template_url_from_agent_policy.test.ts} (51%)
rename x-pack/plugins/fleet/public/services/{get_cloud_formation_template_url_from_agent_policy.ts => get_template_url_from_agent_policy.ts} (66%)
create mode 100644 x-pack/plugins/fleet/public/services/get_template_url_from_package_info.test.ts
rename x-pack/plugins/fleet/public/services/{get_cloud_formation_template_url_from_package_info.ts => get_template_url_from_package_info.ts} (68%)
diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts
index 7f0b4f62fb2165..23de4b6d02e36b 100644
--- a/x-pack/plugins/cloud_security_posture/common/constants.ts
+++ b/x-pack/plugins/cloud_security_posture/common/constants.ts
@@ -10,6 +10,7 @@ import {
VulnSeverity,
AwsCredentialsTypeFieldMap,
GcpCredentialsTypeFieldMap,
+ AzureCredentialsTypeFieldMap,
} from './types';
export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
@@ -156,3 +157,8 @@ export const GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP: GcpCredentialsTypeFieldMap = {
'credentials-file': ['gcp.credentials.file'],
'credentials-json': ['gcp.credentials.json'],
};
+
+export const AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP: AzureCredentialsTypeFieldMap = {
+ manual: [],
+ arm_template: [],
+};
diff --git a/x-pack/plugins/cloud_security_posture/common/types.ts b/x-pack/plugins/cloud_security_posture/common/types.ts
index 036826ba9e6de0..092129a6762e25 100644
--- a/x-pack/plugins/cloud_security_posture/common/types.ts
+++ b/x-pack/plugins/cloud_security_posture/common/types.ts
@@ -30,6 +30,12 @@ export type GcpCredentialsTypeFieldMap = {
[key in GcpCredentialsType]: string[];
};
+export type AzureCredentialsType = 'arm_template' | 'manual';
+
+export type AzureCredentialsTypeFieldMap = {
+ [key in AzureCredentialsType]: string[];
+};
+
export type Evaluation = 'passed' | 'failed' | 'NA';
export type PostureTypes = 'cspm' | 'kspm' | 'vuln_mgmt' | 'all';
diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts
index 1cf006589bd7a0..85815e780b062c 100644
--- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts
+++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts
@@ -20,6 +20,7 @@ import {
CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE,
AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP,
GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP,
+ AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP,
} from '../constants';
import type {
BenchmarkId,
@@ -27,6 +28,7 @@ import type {
BaseCspSetupStatus,
AwsCredentialsType,
GcpCredentialsType,
+ AzureCredentialsType,
RuleSection,
} from '../types';
@@ -119,6 +121,8 @@ export const cleanupCredentials = (packagePolicy: NewPackagePolicy | UpdatePacka
enabledInput?.streams?.[0].vars?.['aws.credentials.type']?.value;
const gcpCredentialType: GcpCredentialsType | undefined =
enabledInput?.streams?.[0].vars?.['gcp.credentials.type']?.value;
+ const azureCredentialType: AzureCredentialsType | undefined =
+ enabledInput?.streams?.[0].vars?.['azure.credentials.type']?.value;
if (awsCredentialType || gcpCredentialType) {
let credsToKeep: string[] = [' '];
@@ -129,6 +133,9 @@ export const cleanupCredentials = (packagePolicy: NewPackagePolicy | UpdatePacka
} else if (gcpCredentialType) {
credsToKeep = GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP[gcpCredentialType];
credFields = Object.values(GCP_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
+ } else if (azureCredentialType) {
+ credsToKeep = AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP[azureCredentialType];
+ credFields = Object.values(AZURE_CREDENTIALS_TYPE_TO_FIELDS_MAP).flat();
}
if (credsToKeep) {
diff --git a/x-pack/plugins/cloud_security_posture/public/common/constants.ts b/x-pack/plugins/cloud_security_posture/public/common/constants.ts
index 4a5d5bfc7bd9a1..eb6318e7c67279 100644
--- a/x-pack/plugins/cloud_security_posture/public/common/constants.ts
+++ b/x-pack/plugins/cloud_security_posture/public/common/constants.ts
@@ -95,6 +95,7 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
icon: googleCloudLogo,
isBeta: true,
},
+ // needs to be a function that disables/enabled based on integration version
{
type: CLOUDBEAT_AZURE,
name: i18n.translate('xpack.csp.cspmIntegration.azureOption.nameTitle', {
@@ -103,11 +104,9 @@ export const cloudPostureIntegrations: CloudPostureIntegrations = {
benchmark: i18n.translate('xpack.csp.cspmIntegration.azureOption.benchmarkTitle', {
defaultMessage: 'CIS Azure',
}),
- disabled: true,
+ disabled: false,
+ isBeta: true,
icon: 'logoAzure',
- tooltip: i18n.translate('xpack.csp.cspmIntegration.azureOption.tooltipContent', {
- defaultMessage: 'Coming soon',
- }),
},
],
},
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx
new file mode 100644
index 00000000000000..51ef9c7bcb955d
--- /dev/null
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/azure_credentials_form.tsx
@@ -0,0 +1,238 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+import React, { useEffect } from 'react';
+import { EuiLink, EuiSpacer, EuiText, EuiTitle, EuiCallOut, EuiHorizontalRule } from '@elastic/eui';
+import type { NewPackagePolicy } from '@kbn/fleet-plugin/public';
+import { NewPackagePolicyInput, PackageInfo } from '@kbn/fleet-plugin/common';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { css } from '@emotion/react';
+import { i18n } from '@kbn/i18n';
+import semverValid from 'semver/functions/valid';
+import semverCoerce from 'semver/functions/coerce';
+import semverLt from 'semver/functions/lt';
+import { SetupFormat, useAzureCredentialsForm } from './hooks';
+import { NewPackagePolicyPostureInput } from '../utils';
+import { CspRadioOption, RadioGroup } from '../csp_boxed_radio_group';
+
+interface AzureSetupInfoContentProps {
+ integrationLink: string;
+}
+
+export const AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE = 'arm_template';
+export const AZURE_MANUAL_CREDENTIAL_TYPE = 'manual';
+
+const AzureSetupInfoContent = ({ integrationLink }: AzureSetupInfoContentProps) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ ),
+ }}
+ />
+
+ >
+ );
+};
+
+const getSetupFormatOptions = (): CspRadioOption[] => [
+ {
+ id: AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
+ label: 'ARM Template',
+ },
+ {
+ id: AZURE_MANUAL_CREDENTIAL_TYPE,
+ label: i18n.translate('xpack.csp.azureIntegration.setupFormatOptions.manual', {
+ defaultMessage: 'Manual',
+ }),
+ disabled: true,
+ tooltip: i18n.translate(
+ 'xpack.csp.azureIntegration.setupFormatOptions.manual.disabledTooltip',
+ { defaultMessage: 'Coming Soon' }
+ ),
+ },
+];
+
+interface Props {
+ newPolicy: NewPackagePolicy;
+ input: Extract;
+ updatePolicy(updatedPolicy: NewPackagePolicy): void;
+ packageInfo: PackageInfo;
+ onChange: any;
+ setIsValid: (isValid: boolean) => void;
+}
+
+const ARM_TEMPLATE_EXTERNAL_DOC_URL =
+ 'https://learn.microsoft.com/en-us/azure/azure-resource-manager/templates/';
+
+const ArmTemplateSetup = ({
+ hasArmTemplateUrl,
+ input,
+}: {
+ hasArmTemplateUrl: boolean;
+ input: NewPackagePolicyInput;
+}) => {
+ if (!hasArmTemplateUrl) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+
+
+ {i18n.translate('xpack.csp.azureIntegration.documentationLinkText', {
+ defaultMessage: 'documentation',
+ })}
+
+ ),
+ }}
+ />
+
+ >
+ );
+};
+
+const AZURE_MINIMUM_PACKAGE_VERSION = '1.6.0';
+
+export const AzureCredentialsForm = ({
+ input,
+ newPolicy,
+ updatePolicy,
+ packageInfo,
+ onChange,
+ setIsValid,
+}: Props) => {
+ const { setupFormat, onSetupFormatChange, integrationLink, hasArmTemplateUrl } =
+ useAzureCredentialsForm({
+ newPolicy,
+ input,
+ packageInfo,
+ onChange,
+ setIsValid,
+ updatePolicy,
+ });
+
+ useEffect(() => {
+ if (!setupFormat) {
+ onSetupFormatChange(AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE);
+ }
+ }, [setupFormat, onSetupFormatChange]);
+
+ const packageSemanticVersion = semverValid(packageInfo.version);
+ const cleanPackageVersion = semverCoerce(packageSemanticVersion) || '';
+ const isPackageVersionValidForAzure = !semverLt(
+ cleanPackageVersion,
+ AZURE_MINIMUM_PACKAGE_VERSION
+ );
+
+ useEffect(() => {
+ setIsValid(isPackageVersionValidForAzure);
+
+ onChange({
+ isValid: isPackageVersionValidForAzure,
+ updatedPolicy: newPolicy,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [input, packageInfo, setupFormat]);
+
+ if (!isPackageVersionValidForAzure) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+ idSelected !== setupFormat && onSetupFormatChange(idSelected)
+ }
+ />
+
+ {setupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE && (
+
+ )}
+
+ >
+ );
+};
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/get_azure_credentials_form_options.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/get_azure_credentials_form_options.tsx
new file mode 100644
index 00000000000000..5a074ad4c4ebc6
--- /dev/null
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/get_azure_credentials_form_options.tsx
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
+import React from 'react';
+import { i18n } from '@kbn/i18n';
+import { AzureCredentialsType } from '../../../../common/types';
+
+export type AzureCredentialsFields = Record;
+
+export interface AzureOptionValue {
+ label: string;
+ info: React.ReactNode;
+ fields: AzureCredentialsFields;
+}
+
+export type AzureOptions = Record;
+
+export const getInputVarsFields = (input: NewPackagePolicyInput, fields: AzureCredentialsFields) =>
+ Object.entries(input.streams[0].vars || {})
+ .filter(([id]) => id in fields)
+ .map(([id, inputVar]) => {
+ const field = fields[id];
+ return {
+ id,
+ label: field.label,
+ type: field.type || 'text',
+ value: inputVar.value,
+ } as const;
+ });
+
+export const DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE = 'manual';
+
+export const getAzureCredentialsFormOptions = (): AzureOptions => ({
+ arm_template: {
+ label: 'ARM Template',
+ info: [],
+ fields: {},
+ },
+ manual: {
+ label: i18n.translate('xpack.csp.azureIntegration.credentialType.manualLabel', {
+ defaultMessage: 'Manual',
+ }),
+ info: [],
+ fields: {},
+ },
+});
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/hooks.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/hooks.ts
new file mode 100644
index 00000000000000..011c39cf8038ee
--- /dev/null
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/azure_credentials_form/hooks.ts
@@ -0,0 +1,176 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useEffect, useRef } from 'react';
+import { NewPackagePolicy, PackageInfo } from '@kbn/fleet-plugin/common';
+import { AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE } from './azure_credentials_form';
+import { cspIntegrationDocsNavigation } from '../../../common/navigation/constants';
+import {
+ getArmTemplateUrlFromCspmPackage,
+ getPosturePolicy,
+ NewPackagePolicyPostureInput,
+} from '../utils';
+import {
+ DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE,
+ getAzureCredentialsFormOptions,
+ getInputVarsFields,
+} from './get_azure_credentials_form_options';
+import { CLOUDBEAT_AZURE } from '../../../../common/constants';
+import { AzureCredentialsType } from '../../../../common/types';
+
+export type SetupFormat = AzureCredentialsType;
+
+const getAzureCredentialsType = (
+ input: Extract
+): AzureCredentialsType | undefined => input.streams[0].vars?.['azure.credentials.type']?.value;
+
+const getAzureArmTemplateUrl = (newPolicy: NewPackagePolicy) => {
+ const template: string | undefined = newPolicy?.inputs?.find((i) => i.type === CLOUDBEAT_AZURE)
+ ?.config?.arm_template_url?.value;
+
+ return template || undefined;
+};
+
+const updateAzureArmTemplateUrlInPolicy = (
+ newPolicy: NewPackagePolicy,
+ updatePolicy: (policy: NewPackagePolicy) => void,
+ templateUrl: string | undefined
+) => {
+ updatePolicy?.({
+ ...newPolicy,
+ inputs: newPolicy.inputs.map((input) => {
+ if (input.type === CLOUDBEAT_AZURE) {
+ return {
+ ...input,
+ config: { arm_template_url: { value: templateUrl } },
+ };
+ }
+ return input;
+ }),
+ });
+};
+
+const useUpdateAzureArmTemplate = ({
+ packageInfo,
+ newPolicy,
+ updatePolicy,
+ setupFormat,
+}: {
+ packageInfo: PackageInfo;
+ newPolicy: NewPackagePolicy;
+ updatePolicy: (policy: NewPackagePolicy) => void;
+ setupFormat: SetupFormat;
+}) => {
+ useEffect(() => {
+ const azureArmTemplateUrl = getAzureArmTemplateUrl(newPolicy);
+
+ if (setupFormat === 'manual') {
+ if (!!azureArmTemplateUrl) {
+ updateAzureArmTemplateUrlInPolicy(newPolicy, updatePolicy, undefined);
+ }
+ return;
+ }
+ const templateUrl = getArmTemplateUrlFromCspmPackage(packageInfo);
+
+ if (templateUrl === '') return;
+
+ if (azureArmTemplateUrl === templateUrl) return;
+
+ updateAzureArmTemplateUrlInPolicy(newPolicy, updatePolicy, templateUrl);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [newPolicy?.vars?.arm_template_url, newPolicy, packageInfo, setupFormat]);
+};
+
+export const useAzureCredentialsForm = ({
+ newPolicy,
+ input,
+ packageInfo,
+ onChange,
+ setIsValid,
+ updatePolicy,
+}: {
+ newPolicy: NewPackagePolicy;
+ input: Extract;
+ packageInfo: PackageInfo;
+ onChange: (opts: any) => void;
+ setIsValid: (isValid: boolean) => void;
+ updatePolicy: (updatedPolicy: NewPackagePolicy) => void;
+}) => {
+ const azureCredentialsType: AzureCredentialsType =
+ getAzureCredentialsType(input) || AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE;
+
+ const options = getAzureCredentialsFormOptions();
+
+ const hasArmTemplateUrl = !!getArmTemplateUrlFromCspmPackage(packageInfo);
+
+ const setupFormat = azureCredentialsType;
+
+ const group = options[azureCredentialsType];
+ const fields = getInputVarsFields(input, group.fields);
+ const fieldsSnapshot = useRef({});
+ const lastManualCredentialsType = useRef(undefined);
+
+ useEffect(() => {
+ const isInvalid = setupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE && !hasArmTemplateUrl;
+ setIsValid(!isInvalid);
+
+ onChange({
+ isValid: !isInvalid,
+ updatedPolicy: newPolicy,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [setupFormat, input.type]);
+
+ const integrationLink = cspIntegrationDocsNavigation.cspm.getStartedPath;
+
+ useUpdateAzureArmTemplate({
+ packageInfo,
+ newPolicy,
+ updatePolicy,
+ setupFormat,
+ });
+
+ const onSetupFormatChange = (newSetupFormat: SetupFormat) => {
+ if (newSetupFormat === AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE) {
+ fieldsSnapshot.current = Object.fromEntries(
+ fields?.map((field) => [field.id, { value: field.value }])
+ );
+
+ lastManualCredentialsType.current = getAzureCredentialsType(input);
+
+ updatePolicy(
+ getPosturePolicy(newPolicy, input.type, {
+ 'azure.credentials.type': {
+ value: AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
+ type: 'text',
+ },
+ ...Object.fromEntries(fields?.map((field) => [field.id, { value: undefined }])),
+ })
+ );
+ } else {
+ updatePolicy(
+ getPosturePolicy(newPolicy, input.type, {
+ 'azure.credentials.type': {
+ value: lastManualCredentialsType.current || DEFAULT_AZURE_MANUAL_CREDENTIALS_TYPE,
+ type: 'text',
+ },
+ ...fieldsSnapshot.current,
+ })
+ );
+ }
+ };
+
+ return {
+ azureCredentialsType,
+ setupFormat,
+ group,
+ fields,
+ integrationLink,
+ hasArmTemplateUrl,
+ onSetupFormatChange,
+ };
+};
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx
index c9ba38eccb2e62..9a50658a6a1f3b 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/csp_boxed_radio_group.tsx
@@ -17,7 +17,7 @@ export interface CspRadioGroupProps {
size?: 's' | 'm';
}
-interface CspRadioOption {
+export interface CspRadioOption {
disabled?: boolean;
id: string;
label: string;
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts
index 2f2d44895db36b..c04114fc834879 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/mocks.ts
@@ -19,6 +19,7 @@ import type { PostureInput } from '../../../common/types';
export const getMockPolicyAWS = () => getPolicyMock(CLOUDBEAT_AWS, 'cspm', 'aws');
export const getMockPolicyGCP = () => getPolicyMock(CLOUDBEAT_GCP, 'cspm', 'gcp');
+export const getMockPolicyAzure = () => getPolicyMock(CLOUDBEAT_AZURE, 'cspm', 'azure');
export const getMockPolicyK8s = () => getPolicyMock(CLOUDBEAT_VANILLA, 'kspm', 'self_managed');
export const getMockPolicyEKS = () => getPolicyMock(CLOUDBEAT_EKS, 'kspm', 'eks');
export const getMockPolicyVulnMgmtAWS = () =>
@@ -102,6 +103,28 @@ export const getMockPackageInfoCspmGCP = (packageVersion = '1.5.2') => {
} as PackageInfo;
};
+export const getMockPackageInfoCspmAzure = (packageVersion = '1.6.0') => {
+ return {
+ version: packageVersion,
+ name: 'cspm',
+ policy_templates: [
+ {
+ title: '',
+ description: '',
+ name: 'cspm',
+ inputs: [
+ {
+ type: CLOUDBEAT_AZURE,
+ title: 'Azure',
+ description: '',
+ vars: [{}],
+ },
+ ],
+ },
+ ],
+ } as PackageInfo;
+};
+
const getPolicyMock = (
type: PostureInput,
posture: string,
@@ -136,6 +159,11 @@ const getPolicyMock = (
'gcp.credentials.type': { type: 'text' },
};
+ const azureVarsMock = {
+ 'azure.account_type': { type: 'text' },
+ 'azure.credentials.type': { type: 'text' },
+ };
+
const dataStream = { type: 'logs', dataset: 'cloud_security_posture.findings' };
return {
@@ -182,7 +210,9 @@ const getPolicyMock = (
type: CLOUDBEAT_AZURE,
policy_template: 'cspm',
enabled: false,
- streams: [{ enabled: false, data_stream: dataStream }],
+ streams: [
+ { enabled: type === CLOUDBEAT_AZURE, data_stream: dataStream, vars: azureVarsMock },
+ ],
},
{
type: CLOUDBEAT_VULN_MGMT_AWS,
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
index c5d4c0e8de3e2c..5cb50fd49bc7ba 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
@@ -14,9 +14,11 @@ import {
import { TestProvider } from '../../test/test_provider';
import {
getMockPackageInfoCspmAWS,
+ getMockPackageInfoCspmAzure,
getMockPackageInfoCspmGCP,
getMockPackageInfoVulnMgmtAWS,
getMockPolicyAWS,
+ getMockPolicyAzure,
getMockPolicyEKS,
getMockPolicyGCP,
getMockPolicyK8s,
@@ -30,7 +32,12 @@ import type {
} from '@kbn/fleet-plugin/common';
import userEvent from '@testing-library/user-event';
import { getPosturePolicy } from './utils';
-import { CLOUDBEAT_AWS, CLOUDBEAT_EKS, CLOUDBEAT_GCP } from '../../../common/constants';
+import {
+ CLOUDBEAT_AWS,
+ CLOUDBEAT_AZURE,
+ CLOUDBEAT_EKS,
+ CLOUDBEAT_GCP,
+} from '../../../common/constants';
import { useParams } from 'react-router-dom';
import { createReactQueryResponse } from '../../test/fixtures/react_query';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
@@ -205,7 +212,7 @@ describe('', () => {
expect(option3).toBeInTheDocument();
expect(option1).toBeEnabled();
expect(option2).toBeEnabled();
- expect(option3).toBeDisabled();
+ expect(option3).toBeEnabled();
expect(option1).toBeChecked();
});
@@ -1130,4 +1137,44 @@ describe('', () => {
});
});
});
+
+ describe('Azure Credentials input fields', () => {
+ it(`renders ${CLOUDBEAT_AZURE} Not supported when version is not at least version 1.6.0`, () => {
+ let policy = getMockPolicyAzure();
+ policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
+ 'azure.credentials.type': { value: 'arm_template' },
+ 'azure.account_type': { value: 'single-account-azure' },
+ });
+
+ const { getByText } = render(
+
+ );
+
+ expect(onChange).toHaveBeenCalledWith({
+ isValid: false,
+ updatedPolicy: policy,
+ });
+
+ expect(
+ getByText(
+ 'CIS Azure is not supported on the current Integration version, please upgrade your integration to the latest version to use CIS Azure'
+ )
+ ).toBeInTheDocument();
+ });
+
+ it(`selects default ${CLOUDBEAT_AZURE} fields`, () => {
+ let policy = getMockPolicyAzure();
+ policy = getPosturePolicy(policy, CLOUDBEAT_AZURE, {
+ 'azure.credentials.type': { value: 'arm_template' },
+ 'azure.account_type': { value: 'single-account-azure' },
+ });
+
+ render();
+
+ expect(onChange).toHaveBeenCalledWith({
+ isValid: true,
+ updatedPolicy: policy,
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
index 9ee0a7ac18f76a..67a617decac761 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
@@ -27,6 +27,7 @@ import type {
import { PackageInfo, PackagePolicy } from '@kbn/fleet-plugin/common';
import { useParams } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
+import { AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE } from './azure_credentials_form/azure_credentials_form';
import { CspRadioGroupProps, RadioGroup } from './csp_boxed_radio_group';
import { assert } from '../../../common/utils/helpers';
import type { PostureInput, CloudSecurityPolicyTemplate } from '../../../common/types';
@@ -83,7 +84,11 @@ export const AWS_SINGLE_ACCOUNT = 'single-account';
export const AWS_ORGANIZATION_ACCOUNT = 'organization-account';
export const GCP_SINGLE_ACCOUNT = 'single-account-gcp';
export const GCP_ORGANIZATION_ACCOUNT = 'organization-account-gcp';
+export const AZURE_SINGLE_ACCOUNT = 'single-account-azure';
+export const AZURE_ORGANIZATION_ACCOUNT = 'organization-account-azure';
+
type AwsAccountType = typeof AWS_SINGLE_ACCOUNT | typeof AWS_ORGANIZATION_ACCOUNT;
+type AzureAccountType = typeof AZURE_SINGLE_ACCOUNT | typeof AZURE_ORGANIZATION_ACCOUNT;
const getAwsAccountTypeOptions = (isAwsOrgDisabled: boolean): CspRadioGroupProps['options'] => [
{
@@ -128,6 +133,28 @@ const getGcpAccountTypeOptions = (): CspRadioGroupProps['options'] => [
},
];
+const getAzureAccountTypeOptions = (): CspRadioGroupProps['options'] => [
+ {
+ id: AZURE_ORGANIZATION_ACCOUNT,
+ label: i18n.translate('xpack.csp.fleetIntegration.azureAccountType.azureOrganizationLabel', {
+ defaultMessage: 'Azure Organization',
+ }),
+ disabled: true,
+ tooltip: i18n.translate(
+ 'xpack.csp.fleetIntegration.azureAccountType.azureOrganizationDisabledTooltip',
+ {
+ defaultMessage: 'Coming Soon',
+ }
+ ),
+ },
+ {
+ id: AZURE_SINGLE_ACCOUNT,
+ label: i18n.translate('xpack.csp.fleetIntegration.azureAccountType.singleAccountLabel', {
+ defaultMessage: 'Single Subscription',
+ }),
+ },
+];
+
const getAwsAccountType = (
input: Extract
): AwsAccountType | undefined => input.streams[0].vars?.['aws.account_type']?.value;
@@ -175,7 +202,7 @@ const AwsAccountTypeSelect = ({
@@ -277,6 +304,89 @@ const GcpAccountTypeSelect = ({
);
};
+const getAzureAccountType = (
+ input: Extract
+): AzureAccountType | undefined => input.streams[0].vars?.['azure.account_type']?.value;
+
+const AzureAccountTypeSelect = ({
+ input,
+ newPolicy,
+ updatePolicy,
+}: {
+ input: Extract;
+ newPolicy: NewPackagePolicy;
+ updatePolicy: (updatedPolicy: NewPackagePolicy) => void;
+}) => {
+ const azureAccountTypeOptions = getAzureAccountTypeOptions();
+
+ useEffect(() => {
+ if (!getAzureAccountType(input)) {
+ updatePolicy(
+ getPosturePolicy(newPolicy, input.type, {
+ 'azure.account_type': {
+ value: AZURE_SINGLE_ACCOUNT,
+ type: 'text',
+ },
+ 'azure.credentials.type': {
+ value: AZURE_ARM_TEMPLATE_CREDENTIAL_TYPE,
+ type: 'text',
+ },
+ })
+ );
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [input, updatePolicy]);
+
+ return (
+ <>
+
+
+
+
+ {
+ updatePolicy(
+ getPosturePolicy(newPolicy, input.type, {
+ 'azure.account_type': {
+ value: accountType,
+ type: 'text',
+ },
+ })
+ );
+ }}
+ size="m"
+ />
+ {getAzureAccountType(input) === AZURE_ORGANIZATION_ACCOUNT && (
+ <>
+
+
+
+
+ >
+ )}
+ {getAzureAccountType(input) === AZURE_SINGLE_ACCOUNT && (
+ <>
+
+
+
+
+ >
+ )}
+ >
+ );
+};
+
const IntegrationSettings = ({ onChange, fields }: IntegrationInfoFieldsProps) => (
{fields.map(({ value, id, label, error }) => (
@@ -303,7 +413,9 @@ export const CspPolicyTemplateForm = memo
onChange({ isValid, updatedPolicy }),
+ (updatedPolicy: NewPackagePolicy) => {
+ onChange({ isValid, updatedPolicy });
+ },
[onChange, isValid]
);
/**
@@ -434,13 +546,6 @@ export const CspPolicyTemplateForm = memo
- {/* Defines the name/description */}
- updatePolicy({ ...newPolicy, [field]: value })}
- />
-
-
{/* AWS account type selection box */}
{input.type === 'cloudbeat/cis_aws' && (
)}
+ {input.type === 'cloudbeat/cis_azure' && (
+
+ )}
+
+ {/* Defines the name/description */}
+
+ updatePolicy({ ...newPolicy, [field]: value })}
+ />
+
{/* Defines the vars of the enabled input of the active policy template */}
;
case 'cloudbeat/cis_gcp':
return ;
+ case 'cloudbeat/cis_azure':
+ return ;
default:
return null;
}
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts
index 7d4233b8016df6..9db4c9bf22a75d 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/utils.ts
@@ -100,7 +100,6 @@ const getPostureInput = (
enabled: isInputEnabled,
// Merge new vars with existing vars
...(isInputEnabled &&
- stream.vars &&
inputVars && {
vars: {
...stream.vars,
@@ -183,6 +182,24 @@ export const getCspmCloudFormationDefaultValue = (packageInfo: PackageInfo): str
return cloudFormationTemplate;
};
+export const getArmTemplateUrlFromCspmPackage = (packageInfo: PackageInfo): string => {
+ if (!packageInfo.policy_templates) return '';
+
+ const policyTemplate = packageInfo.policy_templates.find((p) => p.name === CSPM_POLICY_TEMPLATE);
+ if (!policyTemplate) return '';
+
+ const policyTemplateInputs = hasPolicyTemplateInputs(policyTemplate) && policyTemplate.inputs;
+ if (!policyTemplateInputs) return '';
+
+ const armTemplateUrl = policyTemplateInputs.reduce((acc, input): string => {
+ if (!input.vars) return acc;
+ const template = input.vars.find((v) => v.name === 'arm_template_url')?.default;
+ return template ? String(template) : acc;
+ }, '');
+
+ return armTemplateUrl;
+};
+
/**
* Input vars that are hidden from the user
*/
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_azure_arm_template_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_azure_arm_template_modal.tsx
new file mode 100644
index 00000000000000..747a894a647fe0
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/post_install_azure_arm_template_modal.tsx
@@ -0,0 +1,103 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiCallOut,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiSpacer,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { useQuery } from '@tanstack/react-query';
+
+import { getAzureArmPropsFromPackagePolicy } from '../../../../../../../services/get_azure_arm_props_from_package_policy';
+
+import { useCreateAzureArmTemplateUrl } from '../../../../../../../hooks/use_create_azure_arm_template_url';
+
+import { AzureArmTemplateGuide } from '../../../../../../../components/azure_arm_template_guide';
+
+import type { AgentPolicy, PackagePolicy } from '../../../../../types';
+import { sendGetEnrollmentAPIKeys } from '../../../../../hooks';
+
+export const PostInstallAzureArmTemplateModal: React.FunctionComponent<{
+ onConfirm: () => void;
+ onCancel: () => void;
+ agentPolicy: AgentPolicy;
+ packagePolicy: PackagePolicy;
+}> = ({ onConfirm, onCancel, agentPolicy, packagePolicy }) => {
+ const { data: apyKeysData } = useQuery(['cloudFormationApiKeys'], () =>
+ sendGetEnrollmentAPIKeys({
+ page: 1,
+ perPage: 1,
+ kuery: `policy_id:${agentPolicy.id}`,
+ })
+ );
+
+ const azureArmTemplateProps = getAzureArmPropsFromPackagePolicy(packagePolicy);
+
+ const { azureArmTemplateUrl, error, isError, isLoading } = useCreateAzureArmTemplateUrl({
+ enrollmentAPIKey: apyKeysData?.data?.items[0]?.api_key,
+ azureArmTemplateProps,
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+ {error && isError && (
+ <>
+
+
+ >
+ )}
+
+
+
+
+
+
+ {
+ window.open(azureArmTemplateUrl);
+ onConfirm();
+ }}
+ fill
+ color="primary"
+ isLoading={isLoading}
+ isDisabled={isError}
+ >
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
index da4ecfbe3569aa..f76ef340a2c983 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx
@@ -9,6 +9,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import { safeLoad } from 'js-yaml';
+import { getAzureArmPropsFromPackagePolicy } from '../../../../../../../services/get_azure_arm_props_from_package_policy';
+
import type {
AgentPolicy,
NewPackagePolicy,
@@ -304,12 +306,21 @@ export function useOnSubmit({
force,
});
+ const hasAzureArmTemplate = data?.item
+ ? getAzureArmPropsFromPackagePolicy(data.item).templateUrl
+ : false;
+
const hasCloudFormation = data?.item
? getCloudFormationPropsFromPackagePolicy(data.item).templateUrl
: false;
const hasGoogleCloudShell = data?.item ? getCloudShellUrlFromPackagePolicy(data.item) : false;
+ if (hasAzureArmTemplate) {
+ setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_AZURE_ARM_TEMPLATE');
+ } else {
+ setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS');
+ }
if (hasCloudFormation) {
setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_CLOUD_FORMATION');
} else {
@@ -324,6 +335,10 @@ export function useOnSubmit({
setSavedPackagePolicy(data!.item);
const hasAgentsAssigned = agentCount && agentPolicy;
+ if (!hasAgentsAssigned && hasAzureArmTemplate) {
+ setFormState('SUBMITTED_AZURE_ARM_TEMPLATE');
+ return;
+ }
if (!hasAgentsAssigned && hasCloudFormation) {
setFormState('SUBMITTED_CLOUD_FORMATION');
return;
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
index e7a35ae48dbda6..375a19b41cdc06 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx
@@ -61,6 +61,7 @@ import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from '.
import { useDevToolsRequest, useOnSubmit } from './hooks';
import { PostInstallCloudFormationModal } from './components/post_install_cloud_formation_modal';
import { PostInstallGoogleCloudShellModal } from './components/post_install_google_cloud_shell_modal';
+import { PostInstallAzureArmTemplateModal } from './components/post_install_azure_arm_template_modal';
const StepsWithLessPadding = styled(EuiSteps)`
.euiStep__content {
@@ -415,6 +416,14 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({
onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
/>
)}
+ {formState === 'SUBMITTED_AZURE_ARM_TEMPLATE' && agentPolicy && savedPackagePolicy && (
+ navigateAddAgent(savedPackagePolicy)}
+ onCancel={() => navigateAddAgentHelp(savedPackagePolicy)}
+ />
+ )}
{formState === 'SUBMITTED_CLOUD_FORMATION' && agentPolicy && savedPackagePolicy && (
= ({
+ enrollmentAPIKey,
+ cloudSecurityIntegration,
+}) => {
+ const { isLoading, azureArmTemplateUrl, error, isError } = useCreateAzureArmTemplateUrl({
+ enrollmentAPIKey,
+ azureArmTemplateProps: cloudSecurityIntegration?.azureArmTemplateProps,
+ });
+
+ if (error && isError) {
+ return (
+ <>
+
+
+ >
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx
index 9345e4e3a66635..bc541580ddce7e 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/hooks.tsx
@@ -7,6 +7,10 @@
import { useState, useEffect, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
+import { SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS } from '../../services/get_template_url_from_package_info';
+
+import { SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG } from '../../services/get_template_url_from_agent_policy';
+
import type { PackagePolicy, AgentPolicy } from '../../types';
import { sendGetOneAgentPolicy, useGetPackageInfoByKeyQuery, useStartServices } from '../../hooks';
import {
@@ -14,18 +18,16 @@ import {
FLEET_CLOUD_SECURITY_POSTURE_PACKAGE,
FLEET_CLOUD_DEFEND_PACKAGE,
} from '../../../common';
-import { getCloudShellUrlFromAgentPolicy } from '../../services';
+import { getTemplateUrlFromPackageInfo, getCloudShellUrlFromAgentPolicy } from '../../services';
-import {
- getCloudFormationTemplateUrlFromPackageInfo,
- getCloudFormationTemplateUrlFromAgentPolicy,
-} from '../../services';
+import { getTemplateUrlFromAgentPolicy } from '../../services';
import type {
K8sMode,
CloudSecurityIntegrationType,
CloudSecurityIntegrationAwsAccountType,
CloudSecurityIntegration,
+ CloudSecurityIntegrationAzureAccountType,
} from './types';
// Packages that requires custom elastic-agent manifest
@@ -99,6 +101,9 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
{ enabled: Boolean(cloudSecurityPackagePolicy) }
);
+ const AWS_ACCOUNT_TYPE = 'aws.account_type';
+ const AZURE_ACCOUNT_TYPE = 'azure.account_type';
+
const cloudSecurityIntegration: CloudSecurityIntegration | undefined = useMemo(() => {
if (!agentPolicy || !cloudSecurityPackagePolicy) {
return undefined;
@@ -109,8 +114,15 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
if (!integrationType) return undefined;
- const cloudFormationTemplateFromAgentPolicy =
- getCloudFormationTemplateUrlFromAgentPolicy(agentPolicy);
+ const cloudFormationTemplateFromAgentPolicy = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
+ agentPolicy
+ );
+
+ const azureArmTemplateFromAgentPolicy = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.ARM_TEMPLATE,
+ agentPolicy
+ );
// Use the latest CloudFormation template for the current version
// So it guarantee that the template version matches the integration version
@@ -118,16 +130,31 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
// In case it can't find the template for the current version,
// it will fallback to the one from the agent policy.
const cloudFormationTemplateUrl = packageInfoData?.item
- ? getCloudFormationTemplateUrlFromPackageInfo(packageInfoData.item, integrationType)
+ ? getTemplateUrlFromPackageInfo(
+ packageInfoData.item,
+ integrationType,
+ SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.CLOUD_FORMATION
+ )
: cloudFormationTemplateFromAgentPolicy;
- const AWS_ACCOUNT_TYPE = 'aws.account_type';
-
const cloudFormationAwsAccountType: CloudSecurityIntegrationAwsAccountType | undefined =
cloudSecurityPackagePolicy?.inputs?.find((input) => input.enabled)?.streams?.[0]?.vars?.[
AWS_ACCOUNT_TYPE
]?.value;
+ const azureArmTemplateUrl = packageInfoData?.item
+ ? getTemplateUrlFromPackageInfo(
+ packageInfoData.item,
+ integrationType,
+ SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.ARM_TEMPLATE
+ )
+ : azureArmTemplateFromAgentPolicy;
+
+ const azureArmTemplateAccountType: CloudSecurityIntegrationAzureAccountType | undefined =
+ cloudSecurityPackagePolicy?.inputs?.find((input) => input.enabled)?.streams?.[0]?.vars?.[
+ AZURE_ACCOUNT_TYPE
+ ]?.value;
+
const cloudShellUrl = getCloudShellUrlFromAgentPolicy(agentPolicy);
return {
isLoading,
@@ -137,6 +164,11 @@ export function useCloudSecurityIntegration(agentPolicy?: AgentPolicy) {
awsAccountType: cloudFormationAwsAccountType,
templateUrl: cloudFormationTemplateUrl,
},
+ isAzureArmTemplate: Boolean(azureArmTemplateFromAgentPolicy),
+ azureArmTemplateProps: {
+ azureAccountType: azureArmTemplateAccountType,
+ templateUrl: azureArmTemplateUrl,
+ },
cloudShellUrl,
};
}, [agentPolicy, packageInfoData?.item, isLoading, cloudSecurityPackagePolicy]);
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx
index 0a413856e4f34e..39d4ab5e0428d5 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/instructions.tsx
@@ -82,6 +82,7 @@ export const Instructions = (props: InstructionProps) => {
useEffect(() => {
// If we detect a CloudFormation integration, we want to hide the selection type
if (
+ props.cloudSecurityIntegration?.isAzureArmTemplate ||
props.cloudSecurityIntegration?.isCloudFormation ||
props.cloudSecurityIntegration?.cloudShellUrl
) {
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx
index 12a0025efd46d0..2cf22d7a26dbbc 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/compute_steps.tsx
@@ -38,6 +38,7 @@ import {
InstallManagedAgentStep,
InstallCloudFormationManagedAgentStep,
InstallGoogleCloudShellManagedAgentStep,
+ InstallAzureArmTemplateManagedAgentStep,
IncomingDataConfirmationStep,
} from '.';
@@ -274,6 +275,15 @@ export const ManagedSteps: React.FunctionComponent = ({
cloudShellCommand: installManagedCommands.googleCloudShell,
})
);
+ } else if (cloudSecurityIntegration?.isAzureArmTemplate) {
+ steps.push(
+ InstallAzureArmTemplateManagedAgentStep({
+ selectedApiKeyId,
+ apiKeyData,
+ enrollToken,
+ cloudSecurityIntegration,
+ })
+ );
} else {
steps.push(
InstallManagedAgentStep({
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/index.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/index.tsx
index 8c9adb376f423f..a068cd8de6af51 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/index.tsx
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/index.tsx
@@ -10,6 +10,7 @@ export * from './agent_enrollment_key_selection_step';
export * from './agent_policy_selection_step';
export * from './configure_standalone_agent_step';
export * from './incoming_data_confirmation_step';
+export * from './install_azure_arm_template_managed_agent_step';
export * from './install_cloud_formation_managed_agent_step';
export * from './install_google_cloud_shell_managed_agent_step';
export * from './install_managed_agent_step';
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_azure_arm_template_managed_agent_step.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_azure_arm_template_managed_agent_step.tsx
new file mode 100644
index 00000000000000..a24e958b389161
--- /dev/null
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/steps/install_azure_arm_template_managed_agent_step.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+
+import { i18n } from '@kbn/i18n';
+
+import type { EuiContainedStepProps } from '@elastic/eui/src/components/steps/steps';
+
+import { AzureArmTemplateInstructions } from '../azure_arm_template_instructions';
+
+import type { GetOneEnrollmentAPIKeyResponse } from '../../../../common/types/rest_spec/enrollment_api_key';
+
+import type { CloudSecurityIntegration } from '../types';
+
+export const InstallAzureArmTemplateManagedAgentStep = ({
+ selectedApiKeyId,
+ apiKeyData,
+ enrollToken,
+ isComplete,
+ cloudSecurityIntegration,
+}: {
+ selectedApiKeyId?: string;
+ apiKeyData?: GetOneEnrollmentAPIKeyResponse | null;
+ enrollToken?: string;
+ isComplete?: boolean;
+ cloudSecurityIntegration?: CloudSecurityIntegration | undefined;
+}): EuiContainedStepProps => {
+ const nonCompleteStatus = selectedApiKeyId ? undefined : 'disabled';
+ const status = isComplete ? 'complete' : nonCompleteStatus;
+
+ return {
+ status,
+ title: i18n.translate(
+ 'xpack.fleet.agentEnrollment.azureArmTemplate.stepEnrollAndRunAgentTitle',
+ { defaultMessage: 'Install Elastic Agent on your cloud' }
+ ),
+ children:
+ selectedApiKeyId && apiKeyData && cloudSecurityIntegration ? (
+
+ ) : (
+
+ ),
+ };
+};
diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
index e292182a3cd92a..abad38a0d74ae3 100644
--- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
+++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/types.ts
@@ -17,6 +17,9 @@ export type K8sMode =
export type CloudSecurityIntegrationType = 'kspm' | 'vuln_mgmt' | 'cspm';
export type CloudSecurityIntegrationAwsAccountType = 'single-account' | 'organization-account';
+export type CloudSecurityIntegrationAzureAccountType =
+ | 'single-account-azure'
+ | 'organization-account-azure';
export type FlyoutMode = 'managed' | 'standalone';
export type SelectionType = 'tabs' | 'radio' | undefined;
@@ -26,11 +29,18 @@ export interface CloudFormationProps {
awsAccountType: CloudSecurityIntegrationAwsAccountType | undefined;
}
+export interface AzureArmTemplateProps {
+ templateUrl: string | undefined;
+ azureAccountType: CloudSecurityIntegrationAzureAccountType | undefined;
+}
+
export interface CloudSecurityIntegration {
integrationType: CloudSecurityIntegrationType | undefined;
isLoading: boolean;
isCloudFormation: boolean;
+ isAzureArmTemplate: boolean;
cloudFormationProps?: CloudFormationProps;
+ azureArmTemplateProps?: AzureArmTemplateProps;
cloudShellUrl: string | undefined;
}
diff --git a/x-pack/plugins/fleet/public/components/azure_arm_template_guide.tsx b/x-pack/plugins/fleet/public/components/azure_arm_template_guide.tsx
new file mode 100644
index 00000000000000..7cfb6542a02fef
--- /dev/null
+++ b/x-pack/plugins/fleet/public/components/azure_arm_template_guide.tsx
@@ -0,0 +1,72 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { EuiLink, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+import type { CloudSecurityIntegrationAzureAccountType } from './agent_enrollment_flyout/types';
+
+const azureResourceManagerLink =
+ 'https://azure.microsoft.com/en-us/get-started/azure-portal/resource-manager';
+
+export const AzureArmTemplateGuide = ({
+ azureAccountType,
+}: {
+ azureAccountType?: CloudSecurityIntegrationAzureAccountType;
+}) => {
+ return (
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+ {azureAccountType === 'organization-account-azure' ? (
+ -
+
+
+ ) : (
+ -
+
+
+ )}
+ -
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/fleet/public/hooks/use_create_azure_arm_template_url.ts b/x-pack/plugins/fleet/public/hooks/use_create_azure_arm_template_url.ts
new file mode 100644
index 00000000000000..aed03272c470e4
--- /dev/null
+++ b/x-pack/plugins/fleet/public/hooks/use_create_azure_arm_template_url.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+import type { AzureArmTemplateProps } from '../components/agent_enrollment_flyout/types';
+
+import { useGetSettings } from './use_request';
+
+export const useCreateAzureArmTemplateUrl = ({
+ enrollmentAPIKey,
+ azureArmTemplateProps,
+}: {
+ enrollmentAPIKey: string | undefined;
+ azureArmTemplateProps: AzureArmTemplateProps | undefined;
+}) => {
+ const { data, isLoading } = useGetSettings();
+
+ let isError = false;
+ let error: string | undefined;
+
+ // Default fleet server host
+ const fleetServerHost = data?.item.fleet_server_hosts?.[0];
+
+ if (!fleetServerHost && !isLoading) {
+ isError = true;
+ error = i18n.translate('xpack.fleet.agentEnrollment.cloudFormation.noFleetServerHost', {
+ defaultMessage: 'No Fleet Server host found',
+ });
+ }
+
+ if (!enrollmentAPIKey && !isLoading) {
+ isError = true;
+ error = i18n.translate('xpack.fleet.agentEnrollment.cloudFormation.noApiKey', {
+ defaultMessage: 'No enrollment token found',
+ });
+ }
+
+ const azureArmTemplateUrl = azureArmTemplateProps?.templateUrl;
+
+ return {
+ isLoading,
+ azureArmTemplateUrl,
+ isError,
+ error,
+ };
+};
diff --git a/x-pack/plugins/fleet/public/services/get_azure_arm_props_from_package_policy.ts b/x-pack/plugins/fleet/public/services/get_azure_arm_props_from_package_policy.ts
new file mode 100644
index 00000000000000..f064db2e6fadf6
--- /dev/null
+++ b/x-pack/plugins/fleet/public/services/get_azure_arm_props_from_package_policy.ts
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { CloudSecurityIntegrationAzureAccountType } from '../components/agent_enrollment_flyout/types';
+import type { PackagePolicy } from '../types';
+import type { AzureArmTemplateProps } from '../components/agent_enrollment_flyout/types';
+
+const AZURE_ACCOUNT_TYPE = 'azure.account_type';
+
+/**
+ * Get the Azure Arm Template url from a package policy
+ * It looks for a config with an arm_template_url object present in the enabled inputs of the package policy
+ */
+export const getAzureArmPropsFromPackagePolicy = (
+ packagePolicy?: PackagePolicy
+): AzureArmTemplateProps => {
+ const templateUrl: CloudSecurityIntegrationAzureAccountType | undefined =
+ packagePolicy?.inputs?.find((input) => input.enabled)?.config?.arm_template_url?.value;
+
+ const azureAccountType: CloudSecurityIntegrationAzureAccountType | undefined =
+ packagePolicy?.inputs?.find((input) => input.enabled)?.streams?.[0]?.vars?.[AZURE_ACCOUNT_TYPE]
+ ?.value;
+
+ return {
+ templateUrl,
+ azureAccountType,
+ };
+};
diff --git a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.test.ts b/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.test.ts
deleted file mode 100644
index 8ed2fb3ae389aa..00000000000000
--- a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.test.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { getCloudFormationTemplateUrlFromPackageInfo } from './get_cloud_formation_template_url_from_package_info';
-
-describe('getCloudFormationTemplateUrlFromPackageInfo', () => {
- test('returns undefined when packageInfo is undefined', () => {
- const result = getCloudFormationTemplateUrlFromPackageInfo(undefined, 'test');
- expect(result).toBeUndefined();
- });
-
- test('returns undefined when packageInfo has no policy_templates', () => {
- const packageInfo = { inputs: [] };
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'test');
- expect(result).toBeUndefined();
- });
-
- test('returns undefined when integrationType is not found in policy_templates', () => {
- const packageInfo = { policy_templates: [{ name: 'template1' }, { name: 'template2' }] };
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'nonExistentTemplate');
- expect(result).toBeUndefined();
- });
-
- test('returns undefined when no input in the policy template has a cloudFormationTemplate', () => {
- const packageInfo = {
- policy_templates: [
- {
- name: 'template1',
- inputs: [
- { name: 'input1', vars: [] },
- { name: 'input2', vars: [{ name: 'var1', default: 'value1' }] },
- ],
- },
- ],
- };
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'template1');
- expect(result).toBeUndefined();
- });
-
- test('returns the cloudFormationTemplate from the policy template', () => {
- const packageInfo = {
- policy_templates: [
- {
- name: 'template1',
- inputs: [
- { name: 'input1', vars: [] },
- {
- name: 'input2',
- vars: [{ name: 'cloud_formation_template', default: 'cloud_formation_template_url' }],
- },
- ],
- },
- ],
- };
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromPackageInfo(packageInfo, 'template1');
- expect(result).toBe('cloud_formation_template_url');
- });
-});
diff --git a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_agent_policy.test.ts b/x-pack/plugins/fleet/public/services/get_template_url_from_agent_policy.test.ts
similarity index 51%
rename from x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_agent_policy.test.ts
rename to x-pack/plugins/fleet/public/services/get_template_url_from_agent_policy.test.ts
index 6b4214044f2a0c..279f64f412d903 100644
--- a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_agent_policy.test.ts
+++ b/x-pack/plugins/fleet/public/services/get_template_url_from_agent_policy.test.ts
@@ -4,18 +4,26 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { getCloudFormationTemplateUrlFromAgentPolicy } from './get_cloud_formation_template_url_from_agent_policy';
+import {
+ getTemplateUrlFromAgentPolicy,
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG,
+} from './get_template_url_from_agent_policy';
-describe('getCloudFormationTemplateUrlFromAgentPolicy', () => {
+describe('getTemplateUrlFromAgentPolicy', () => {
it('should return undefined when selectedPolicy is undefined', () => {
- const result = getCloudFormationTemplateUrlFromAgentPolicy();
+ const result = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION
+ );
expect(result).toBeUndefined();
});
it('should return undefined when selectedPolicy has no package_policies', () => {
const selectedPolicy = {};
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromAgentPolicy(selectedPolicy);
+ const result = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
+ // @ts-expect-error
+ selectedPolicy
+ );
expect(result).toBeUndefined();
});
@@ -37,8 +45,11 @@ describe('getCloudFormationTemplateUrlFromAgentPolicy', () => {
},
],
};
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromAgentPolicy(selectedPolicy);
+ const result = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
+ // @ts-expect-error
+ selectedPolicy
+ );
expect(result).toBeUndefined();
});
@@ -61,8 +72,38 @@ describe('getCloudFormationTemplateUrlFromAgentPolicy', () => {
},
],
};
- // @ts-expect-error
- const result = getCloudFormationTemplateUrlFromAgentPolicy(selectedPolicy);
+ const result = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.CLOUD_FORMATION,
+ // @ts-expect-error
+ selectedPolicy
+ );
+ expect(result).toBe('url3');
+ });
+
+ it('should return the first config.arm_template_url when available', () => {
+ const selectedPolicy = {
+ package_policies: [
+ {
+ inputs: [
+ { enabled: false, config: { arm_template_url: { value: 'url1' } } },
+ { enabled: false, config: { arm_template_url: { value: 'url2' } } },
+ { enabled: false, config: { other_property: 'value' } },
+ ],
+ },
+ {
+ inputs: [
+ { enabled: false, config: {} },
+ { enabled: true, config: { arm_template_url: { value: 'url3' } } },
+ { enabled: true, config: { arm_template_url: { value: 'url4' } } },
+ ],
+ },
+ ],
+ };
+ const result = getTemplateUrlFromAgentPolicy(
+ SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG.ARM_TEMPLATE,
+ // @ts-expect-error
+ selectedPolicy
+ );
expect(result).toBe('url3');
});
});
diff --git a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_agent_policy.ts b/x-pack/plugins/fleet/public/services/get_template_url_from_agent_policy.ts
similarity index 66%
rename from x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_agent_policy.ts
rename to x-pack/plugins/fleet/public/services/get_template_url_from_agent_policy.ts
index 81aaf5b3fd9708..203d267ecc6fa0 100644
--- a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_agent_policy.ts
+++ b/x-pack/plugins/fleet/public/services/get_template_url_from_agent_policy.ts
@@ -7,12 +7,15 @@
import type { AgentPolicy } from '../types';
-/**
- * Get the cloud formation template url from a agent policy
- * It looks for a config with a cloud_formation_template_url object present in
- * the enabled package_policies inputs of the agent policy
- */
-export const getCloudFormationTemplateUrlFromAgentPolicy = (selectedPolicy?: AgentPolicy) => {
+export const SUPPORTED_TEMPLATES_URL_FROM_AGENT_POLICY_CONFIG = {
+ CLOUD_FORMATION: 'cloud_formation_template_url',
+ ARM_TEMPLATE: 'arm_template_url',
+};
+
+export const getTemplateUrlFromAgentPolicy = (
+ templateUrlFieldName: string,
+ selectedPolicy?: AgentPolicy
+) => {
const cloudFormationTemplateUrl = selectedPolicy?.package_policies?.reduce(
(acc, packagePolicy) => {
const findCloudFormationTemplateUrlConfig = packagePolicy.inputs?.reduce(
@@ -20,8 +23,8 @@ export const getCloudFormationTemplateUrlFromAgentPolicy = (selectedPolicy?: Age
if (accInput !== '') {
return accInput;
}
- if (input?.enabled && input?.config?.cloud_formation_template_url) {
- return input.config.cloud_formation_template_url.value;
+ if (input?.enabled && input?.config?.[templateUrlFieldName]) {
+ return input.config[templateUrlFieldName].value;
}
return accInput;
},
diff --git a/x-pack/plugins/fleet/public/services/get_template_url_from_package_info.test.ts b/x-pack/plugins/fleet/public/services/get_template_url_from_package_info.test.ts
new file mode 100644
index 00000000000000..f2cb8e6987ea76
--- /dev/null
+++ b/x-pack/plugins/fleet/public/services/get_template_url_from_package_info.test.ts
@@ -0,0 +1,112 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import type { PackageInfo } from '../types';
+
+import {
+ getTemplateUrlFromPackageInfo,
+ SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS,
+} from './get_template_url_from_package_info';
+
+describe('getTemplateUrlFromPackageInfo', () => {
+ test('returns undefined when packageInfo is undefined', () => {
+ const result = getTemplateUrlFromPackageInfo(undefined, 'test', 'cloud_formation_template_url');
+ expect(result).toBeUndefined();
+ });
+
+ test('returns undefined when packageInfo has no policy_templates', () => {
+ const packageInfo = { inputs: [] } as unknown as PackageInfo;
+ const result = getTemplateUrlFromPackageInfo(
+ packageInfo,
+ 'test',
+ 'cloud_formation_template_url'
+ );
+ expect(result).toBeUndefined();
+ });
+
+ test('returns undefined when integrationType is not found in policy_templates', () => {
+ const packageInfo = {
+ policy_templates: [{ name: 'template1' }, { name: 'template2' }],
+ } as PackageInfo;
+ const result = getTemplateUrlFromPackageInfo(
+ packageInfo,
+ 'nonExistentTemplate',
+ 'cloud_formation_template_url'
+ );
+ expect(result).toBeUndefined();
+ });
+
+ test('returns undefined when no input in the policy template has a cloudFormationTemplate', () => {
+ const packageInfo = {
+ policy_templates: [
+ {
+ name: 'template1',
+ inputs: [
+ { name: 'input1', vars: [] },
+ { name: 'input2', vars: [{ name: 'var1', default: 'value1' }] },
+ ],
+ },
+ ],
+ } as unknown as PackageInfo;
+
+ const result = getTemplateUrlFromPackageInfo(
+ packageInfo,
+ 'template1',
+ 'cloud_formation_template_url'
+ );
+ expect(result).toBeUndefined();
+ });
+
+ test('returns the cloudFormationTemplate from the policy template', () => {
+ const packageInfo = {
+ policy_templates: [
+ {
+ name: 'template1',
+ inputs: [
+ { name: 'input1', vars: [] },
+ {
+ name: 'input2',
+ vars: [
+ {
+ name: SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.CLOUD_FORMATION,
+ default: 'cloud_formation_template_url',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ } as unknown as PackageInfo;
+
+ const result = getTemplateUrlFromPackageInfo(
+ packageInfo,
+ 'template1',
+ SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS.CLOUD_FORMATION
+ );
+ expect(result).toBe('cloud_formation_template_url');
+ });
+
+ test('returns the armTemplateUrl from the policy template', () => {
+ const packageInfo = {
+ policy_templates: [
+ {
+ name: 'template1',
+ inputs: [
+ { name: 'input1', vars: [] },
+ {
+ name: 'input2',
+ vars: [{ name: 'arm_template_url', default: 'arm_template_url_value' }],
+ },
+ ],
+ },
+ ],
+ } as unknown as PackageInfo;
+
+ const result = getTemplateUrlFromPackageInfo(packageInfo, 'template1', 'arm_template_url');
+ expect(result).toBe('arm_template_url_value');
+ });
+});
diff --git a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.ts b/x-pack/plugins/fleet/public/services/get_template_url_from_package_info.ts
similarity index 68%
rename from x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.ts
rename to x-pack/plugins/fleet/public/services/get_template_url_from_package_info.ts
index 4f5381ccedb3f5..cfb7b747d0427a 100644
--- a/x-pack/plugins/fleet/public/services/get_cloud_formation_template_url_from_package_info.ts
+++ b/x-pack/plugins/fleet/public/services/get_template_url_from_package_info.ts
@@ -7,14 +7,15 @@
import type { PackageInfo } from '../types';
-/**
- * Get the cloud formation template url from the PackageInfo
- * It looks for a input var with a object containing cloud_formation_template_url present in
- * the package_policies inputs of the given integration type
- */
-export const getCloudFormationTemplateUrlFromPackageInfo = (
+export const SUPPORTED_TEMPLATES_URL_FROM_PACKAGE_INFO_INPUT_VARS = {
+ CLOUD_FORMATION: 'cloud_formation_template',
+ ARM_TEMPLATE: 'arm_template_url',
+};
+
+export const getTemplateUrlFromPackageInfo = (
packageInfo: PackageInfo | undefined,
- integrationType: string
+ integrationType: string,
+ templateUrlFieldName: string
): string | undefined => {
if (!packageInfo?.policy_templates) return undefined;
@@ -24,7 +25,7 @@ export const getCloudFormationTemplateUrlFromPackageInfo = (
if ('inputs' in policyTemplate) {
const cloudFormationTemplate = policyTemplate.inputs?.reduce((acc, input): string => {
if (!input.vars) return acc;
- const template = input.vars.find((v) => v.name === 'cloud_formation_template')?.default;
+ const template = input.vars.find((v) => v.name === templateUrlFieldName)?.default;
return template ? String(template) : acc;
}, '');
return cloudFormationTemplate !== '' ? cloudFormationTemplate : undefined;
diff --git a/x-pack/plugins/fleet/public/services/index.ts b/x-pack/plugins/fleet/public/services/index.ts
index a98d4126d52f3e..64009e4a11061d 100644
--- a/x-pack/plugins/fleet/public/services/index.ts
+++ b/x-pack/plugins/fleet/public/services/index.ts
@@ -49,7 +49,7 @@ export { pkgKeyFromPackageInfo } from './pkg_key_from_package_info';
export { createExtensionRegistrationCallback } from './ui_extensions';
export { incrementPolicyName } from './increment_policy_name';
export { getCloudFormationPropsFromPackagePolicy } from './get_cloud_formation_props_from_package_policy';
-export { getCloudFormationTemplateUrlFromAgentPolicy } from './get_cloud_formation_template_url_from_agent_policy';
-export { getCloudFormationTemplateUrlFromPackageInfo } from './get_cloud_formation_template_url_from_package_info';
+export { getTemplateUrlFromAgentPolicy } from './get_template_url_from_agent_policy';
+export { getTemplateUrlFromPackageInfo } from './get_template_url_from_package_info';
export { getCloudShellUrlFromPackagePolicy } from './get_cloud_shell_url_from_package_policy';
export { getCloudShellUrlFromAgentPolicy } from './get_cloud_shell_url_from_agent_policy';
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 0818edbb72eee5..aa95ebb6e2aef1 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -11359,7 +11359,6 @@
"xpack.csp.cspmIntegration.awsOption.nameTitle": "Amazon Web Services",
"xpack.csp.cspmIntegration.azureOption.benchmarkTitle": "CIS Azure",
"xpack.csp.cspmIntegration.azureOption.nameTitle": "Azure",
- "xpack.csp.cspmIntegration.azureOption.tooltipContent": "Bientôt disponible",
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
"xpack.csp.cspmIntegration.integration.nameTitle": "Gestion du niveau de sécurité du cloud",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 18016ac4caa29d..0c085d75d79023 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -11374,7 +11374,6 @@
"xpack.csp.cspmIntegration.awsOption.nameTitle": "Amazon Web Services",
"xpack.csp.cspmIntegration.azureOption.benchmarkTitle": "CIS Azure",
"xpack.csp.cspmIntegration.azureOption.nameTitle": "Azure",
- "xpack.csp.cspmIntegration.azureOption.tooltipContent": "まもなくリリース",
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
"xpack.csp.cspmIntegration.integration.nameTitle": "クラウドセキュリティ態勢管理",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 178fa11a295833..4cc2eb901cbbd4 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -11374,7 +11374,6 @@
"xpack.csp.cspmIntegration.awsOption.nameTitle": "Amazon Web Services",
"xpack.csp.cspmIntegration.azureOption.benchmarkTitle": "CIS Azure",
"xpack.csp.cspmIntegration.azureOption.nameTitle": "Azure",
- "xpack.csp.cspmIntegration.azureOption.tooltipContent": "即将推出",
"xpack.csp.cspmIntegration.gcpOption.benchmarkTitle": "CIS GCP",
"xpack.csp.cspmIntegration.gcpOption.nameTitle": "GCP",
"xpack.csp.cspmIntegration.integration.nameTitle": "云安全态势管理",
From fb13b2181db275f8145bf13f31fb7846d7a3e80b Mon Sep 17 00:00:00 2001
From: Mashhur <99575341+mashhurs@users.noreply.github.com>
Date: Wed, 27 Sep 2023 12:44:32 -0700
Subject: [PATCH 04/22] Migrate deprecated components in Logstash pipelines
section. (#161512)
## Summary
Migrates the deprecated components in Logstash pipelines view based on
[the gude](https://elastic.github.io/eui/#/layout/page-components).
A list of deprecated components in Logstash pipelines can be found
[here](https://github.com/search?q=repo%3Aelastic%2Fkibana+%2FEuiPage%5Ba-zA-Z%5D%2B_Deprecated%2F+path%3A%2F%5Ex-pack%5C%2Fplugins%5C%2Flogstash%2F&type=code).
### Tests
Opened a dev tools and proceeded manual tests on local dev env (based on
[local env setup
guide](https://docs.elastic.dev/kibana-dev-docs/getting-started/setup-dev-env)):
- create pipeline(s)
- check list of pipeline(s)
- delete pipeline(s)
- check alerts when deleting the pipeline
- check Elasticsearch .logstash_pipelines index (`GET
_logstash/pipeline`) to check the data shape which Logstash polls and
uses.
- Closes: #161417
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
~~- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials~~
- [x ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
~~- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard
accessibility](https://webaim.org/techniques/keyboard/))~~
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
~~- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~~
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
### Risk Matrix
Delete this section if it is not applicable to this PR.
Before closing this PR, invite QA, stakeholders, and other developers to
identify risks that should be tested prior to the change/feature
release.
When forming the risk matrix, consider some of the following examples
and how they may potentially impact the change:
| Risk | Probability | Severity | Mitigation/Notes |
|---------------------------|-------------|----------|-------------------------|
| Multiple Spaces—unexpected behavior in non-default Kibana Space.
| Low | High | Integration tests will verify that all features are still
supported in non-default Kibana Space and when user switches between
spaces. |
| Multiple nodes—Elasticsearch polling might have race conditions
when multiple Kibana nodes are polling for the same tasks. | High | Low
| Tasks are idempotent, so executing them multiple times will not result
in logical error, but will degrade performance. To test for this case we
add plenty of unit tests around this logic and document manual testing
procedure. |
| Code should gracefully handle cases when feature X or plugin Y are
disabled. | Medium | High | Unit tests will verify that any feature flag
or plugin combination still results in our service operational. |
| [See more potential risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) |
### For maintainers
- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../pipeline_editor.test.js.snap | 24 +++++++++----------
.../pipeline_editor/pipeline_editor.js | 6 ++---
.../components/pipeline_list/pipeline_list.js | 6 ++---
3 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap
index 2bca876b670829..f635f810d1cae9 100644
--- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap
+++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/__snapshots__/pipeline_editor.test.js.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PipelineEditor component includes required error message for falsy pipeline id 1`] = `
-
-
+
`;
exports[`PipelineEditor component invalidates form for invalid pipeline id input 1`] = `
-
-
+
`;
exports[`PipelineEditor component invalidates form for pipeline id with spaces 1`] = `
-
-
+
`;
exports[`PipelineEditor component matches snapshot for clone pipeline 1`] = `
-
-
+
`;
exports[`PipelineEditor component matches snapshot for create pipeline 1`] = `
-
-
+
`;
exports[`PipelineEditor component matches snapshot for edit pipeline 1`] = `
-
-
+
`;
diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
index 0f0cf333b6ebd9..a47672fd8a2292 100644
--- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
+++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.js
@@ -22,7 +22,7 @@ import {
EuiFieldText,
EuiForm,
EuiFormRow,
- EuiPageContentBody_Deprecated as EuiPageContentBody,
+ EuiPageSection,
EuiSelect,
EuiSpacer,
EuiPageHeader,
@@ -266,7 +266,7 @@ class PipelineEditorUi extends React.Component {
const { intl } = this.props;
return (
-
)}
-
+
);
}
}
diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js
index 0729ad5ce8b576..b80c0522f9aa15 100644
--- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js
+++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.js
@@ -12,7 +12,7 @@ import {
EuiCallOut,
EuiEmptyPrompt,
EuiLoadingSpinner,
- EuiPageContentBody_Deprecated as EuiPageContentBody,
+ EuiPageSection,
EuiPageHeader,
EuiSpacer,
} from '@elastic/eui';
@@ -291,7 +291,7 @@ class PipelineListUi extends React.Component {
const { clonePipeline, createPipeline, isReadOnly, openPipeline } = this.props;
const { isSelectable, message, pipelines, selection, showConfirmDeleteModal } = this.state;
return (
-
+
@@ -329,7 +329,7 @@ class PipelineListUi extends React.Component {
showAddRoleAlert={this.state.showAddRoleAlert}
showEnableMonitoringAlert={this.state.showEnableMonitoringAlert}
/>
-
+
);
}
}
From f9c26208b7a38152a215a7934677f0bdc68d0608 Mon Sep 17 00:00:00 2001
From: Brad White
Date: Wed, 27 Sep 2023 14:14:01 -0600
Subject: [PATCH 05/22] Fix some type issues in x-pack/test (#167343)
## Summary
We're breaking #166813 up into smaller PRs in the interest of getting
PRs through sooner for type fixes. These are the changes for
`x-pack/test`.
---------
Co-authored-by: Thomas Watson
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Alex Szabo
---
.../create_with_circuit_breaker.ts | 4 ++--
.../tests/alerting/suggestions_value_alert.ts | 1 -
.../management/ingest_pipelines/lib/fixtures.ts | 4 ++--
.../apis/security_solution/authentications.ts | 2 +-
.../apm_api_integration/common/bettertest.ts | 4 ++--
.../test/apm_api_integration/common/registry.ts | 2 +-
.../tests/diagnostics/indices.spec.ts | 16 ++++++++--------
.../pages/findings.ts | 4 ++++
.../exception_operators_data_types/ip_array.ts | 8 ++++----
.../exception_operators_data_types/text_array.ts | 8 ++++----
x-pack/test/disable_ems/tests/fonts.ts | 1 +
.../apis/download_sources/crud.ts | 2 +-
.../apis/fleet_telemetry.ts | 6 +++++-
.../drilldowns/explore_data_chart_action.ts | 6 +++---
.../index_pattern/continuous_transform.ts | 1 +
.../page_objects/tag_management_page.ts | 1 -
x-pack/test/functional/services/ml/api.ts | 5 ++---
17 files changed, 41 insertions(+), 34 deletions(-)
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts
index 8183f6b48f4edb..bf1a0792a0091f 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/schedule_circuit_breaker/create_with_circuit_breaker.ts
@@ -26,7 +26,7 @@ export default function createWithCircuitBreakerTests({ getService }: FtrProvide
.expect(200);
objectRemover.add('space1', createdRule.id, 'rule', 'alerting');
- const { body } = await supertest
+ await supertest
.post(`${getUrlPrefix('space1')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
@@ -41,7 +41,7 @@ export default function createWithCircuitBreakerTests({ getService }: FtrProvide
.expect(200);
objectRemover.add('space1', createdRule.id, 'rule', 'alerting');
- const { body } = await supertest
+ await supertest
.post(`${getUrlPrefix('space2')}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestRuleData({ schedule: { interval: '10s' } }))
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts
index 3980c174528ed8..1478a02454bb06 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/suggestions_value_alert.ts
@@ -17,7 +17,6 @@ export default function createRuleSuggestionValuesTests({ getService }: FtrProvi
describe('alerts/suggestions/values', async () => {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
- const supertestWithoutAuth = getService('supertestWithoutAuth');
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/fixtures.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/fixtures.ts
index c148101749085b..344b6fab0f07f9 100644
--- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/fixtures.ts
+++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/fixtures.ts
@@ -12,7 +12,7 @@ import {
IngestPutPipelineRequest,
} from '@elastic/elasticsearch/lib/api/types';
-interface Pipeline {
+export interface Pipeline {
name: string;
description?: string;
onFailureProcessors?: IngestProcessorContainer[];
@@ -21,7 +21,7 @@ interface Pipeline {
metadata?: Metadata;
}
-interface IngestPutPipelineInternalRequest extends Omit {
+export interface IngestPutPipelineInternalRequest extends Omit {
name: string;
}
diff --git a/x-pack/test/api_integration/apis/security_solution/authentications.ts b/x-pack/test/api_integration/apis/security_solution/authentications.ts
index aa54114e04d72d..b65c93995156b8 100644
--- a/x-pack/test/api_integration/apis/security_solution/authentications.ts
+++ b/x-pack/test/api_integration/apis/security_solution/authentications.ts
@@ -9,10 +9,10 @@ import expect from '@kbn/expect';
import {
AuthStackByField,
Direction,
- UserAuthenticationsRequestOptions,
UserAuthenticationsStrategyResponse,
UsersQueries,
} from '@kbn/security-solution-plugin/common/search_strategy';
+import type { UserAuthenticationsRequestOptions } from '@kbn/security-solution-plugin/common/api/search_strategy';
import { FtrProviderContext } from '../../ftr_provider_context';
diff --git a/x-pack/test/apm_api_integration/common/bettertest.ts b/x-pack/test/apm_api_integration/common/bettertest.ts
index 83f7da5725db84..e1132be3f9a771 100644
--- a/x-pack/test/apm_api_integration/common/bettertest.ts
+++ b/x-pack/test/apm_api_integration/common/bettertest.ts
@@ -20,7 +20,7 @@ interface BetterTestOptions {
body?: any;
}
-interface BetterTestResponse {
+export interface BetterTestResponse {
status: number;
body: T;
}
@@ -72,7 +72,7 @@ export class BetterTestError extends Error {
const req = res.req as any;
super(
`Unhandled BetterTestError:
-Status: "${res.status}"
+Status: "${res.status}"
Path: "${req.method} ${req.path}"
Body: ${JSON.stringify(res.body)}`
);
diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts
index 6876854b5991dc..6ae932f59e2a47 100644
--- a/x-pack/test/apm_api_integration/common/registry.ts
+++ b/x-pack/test/apm_api_integration/common/registry.ts
@@ -153,7 +153,7 @@ export function RegistryProvider({ getService }: FtrProviderContext) {
await supertest
.get('/api/ml/saved_objects/sync')
.set('kbn-xsrf', 'foo')
- .auth(ApmUsername.editorUser, kbnTestConfig.getUrlParts().password);
+ .auth(ApmUsername.editorUser, kbnTestConfig.getUrlParts().password!);
}
if (condition.archives.length) {
log('Loaded all archives');
diff --git a/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts b/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts
index 3314b4fdcfcfc8..7822acfe923ff7 100644
--- a/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts
+++ b/x-pack/test/apm_api_integration/tests/diagnostics/indices.spec.ts
@@ -61,7 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
expect(status).to.be(200);
- expect(body.validIndices.length).to.be.greaterThan(0);
+ expect(body.validIndices?.length).to.be.greaterThan(0);
expect(body.invalidIndices).to.eql([]);
});
});
@@ -102,7 +102,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
expect(status).to.be(200);
- expect(body.validIndices.length).to.be.greaterThan(0);
+ expect(body.validIndices?.length).to.be.greaterThan(0);
expect(body.invalidIndices).to.eql([
{
isValid: false,
@@ -158,10 +158,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
expect(status).to.be(200);
- expect(body.validIndices.length).to.be.greaterThan(0);
- expect(body.invalidIndices.length).to.be(1);
+ expect(body.validIndices?.length).to.be.greaterThan(0);
+ expect(body.invalidIndices?.length).to.be(1);
- expect(omit(body.invalidIndices[0], 'index')).to.eql({
+ expect(omit(body.invalidIndices?.[0], 'index')).to.eql({
isValid: false,
fieldMappings: { isValid: true },
ingestPipeline: { isValid: false },
@@ -187,9 +187,9 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
expect(status).to.be(200);
- expect(body.validIndices.length).to.be.greaterThan(0);
- expect(body.invalidIndices.length).to.be(1);
- expect(omit(body.invalidIndices[0], 'index')).to.eql({
+ expect(body.validIndices?.length).to.be.greaterThan(0);
+ expect(body.invalidIndices?.length).to.be(1);
+ expect(omit(body.invalidIndices?.[0], 'index')).to.eql({
isValid: false,
fieldMappings: { isValid: true },
ingestPipeline: { isValid: false, id: 'logs-default-pipeline' },
diff --git a/x-pack/test/cloud_security_posture_functional/pages/findings.ts b/x-pack/test/cloud_security_posture_functional/pages/findings.ts
index 5f85b38d39dd69..5caedd4a6e7f28 100644
--- a/x-pack/test/cloud_security_posture_functional/pages/findings.ts
+++ b/x-pack/test/cloud_security_posture_functional/pages/findings.ts
@@ -22,6 +22,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
// We intentionally make some fields start with a capital letter to test that the query bar is case-insensitive/case-sensitive
const data = [
{
+ '@timestamp': '1695819664234',
resource: { id: chance.guid(), name: `kubelet`, sub_type: 'lower case sub type' },
result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' },
rule: {
@@ -38,6 +39,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
cluster_id: 'Upper case cluster id',
},
{
+ '@timestamp': '1695819673242',
resource: { id: chance.guid(), name: `Pod`, sub_type: 'Upper case sub type' },
result: { evaluation: chance.integer() % 2 === 0 ? 'passed' : 'failed' },
rule: {
@@ -54,6 +56,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
cluster_id: 'Another Upper case cluster id',
},
{
+ '@timestamp': '1695819676242',
resource: { id: chance.guid(), name: `process`, sub_type: 'another lower case type' },
result: { evaluation: 'passed' },
rule: {
@@ -70,6 +73,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
cluster_id: 'lower case cluster id',
},
{
+ '@timestamp': '1695819680202',
resource: { id: chance.guid(), name: `process`, sub_type: 'Upper case type again' },
result: { evaluation: 'failed' },
rule: {
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts
index d828b332d215ad..0fcdf1a03b14bd 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/ip_array.ts
@@ -152,7 +152,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
- expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(ips.flat(10)).to.eql([]);
});
it('should filter a CIDR range of "127.0.0.1/30"', async () => {
@@ -347,7 +347,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
- expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(ips.flat(10)).to.eql([]);
});
});
@@ -409,7 +409,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
- expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(ips.flat(10)).to.eql([]);
});
});
@@ -514,7 +514,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const ips = signalsOpen.hits.hits.map((hit) => hit._source?.ip).sort();
- expect(ips.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(ips.flat(10)).to.eql([]);
});
it('will return 2 results if we have a list which contains the CIDR ranges of "127.0.0.1/32, 127.0.0.2/31, 127.0.0.4/30"', async () => {
diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts
index 27d28c3630293d..fe4a13fcc3c846 100644
--- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts
+++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group9/exception_operators_data_types/text_array.ts
@@ -152,7 +152,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort();
- expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(hits.flat(10)).to.eql([]);
});
});
@@ -280,7 +280,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort();
- expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(hits.flat(10)).to.eql([]);
});
});
@@ -342,7 +342,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort();
- expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(hits.flat(10)).to.eql([]);
});
});
@@ -522,7 +522,7 @@ export default ({ getService }: FtrProviderContext) => {
await waitForSignalsToBePresent(supertest, log, 1, [id]);
const signalsOpen = await getSignalsById(supertest, log, id);
const hits = signalsOpen.hits.hits.map((hit) => hit._source?.text).sort();
- expect(hits.flat(Number.MAX_SAFE_INTEGER)).to.eql([]);
+ expect(hits.flat(10)).to.eql([]);
});
});
diff --git a/x-pack/test/disable_ems/tests/fonts.ts b/x-pack/test/disable_ems/tests/fonts.ts
index aec0b0a6d6802e..b8b6d6903db5e6 100644
--- a/x-pack/test/disable_ems/tests/fonts.ts
+++ b/x-pack/test/disable_ems/tests/fonts.ts
@@ -6,6 +6,7 @@
*/
import expect from '@kbn/expect';
+import type { FtrProviderContext } from '../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects(['maps']);
diff --git a/x-pack/test/fleet_api_integration/apis/download_sources/crud.ts b/x-pack/test/fleet_api_integration/apis/download_sources/crud.ts
index 587a4b7fc884c8..8b0e5ca8c4e774 100644
--- a/x-pack/test/fleet_api_integration/apis/download_sources/crud.ts
+++ b/x-pack/test/fleet_api_integration/apis/download_sources/crud.ts
@@ -357,7 +357,7 @@ export default function (providerContext: FtrProviderContext) {
'https://some.source.proxy:3232'
);
- const res = await supertest
+ await supertest
.put(`/api/fleet/agent_download_sources/${downloadSourceId}`)
.set('kbn-xsrf', 'xxxx')
.send({
diff --git a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
index e0c6799b5a3cd2..3f5c414f46ef87 100644
--- a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
+++ b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts
@@ -126,7 +126,11 @@ export default function (providerContext: FtrProviderContext) {
);
});
- async function waitForAgents(expectedAgentCount: number, attempts: number, _attemptsMade = 0) {
+ async function waitForAgents(
+ expectedAgentCount: number,
+ attempts: number,
+ _attemptsMade = 0
+ ): Promise {
const { body: apiResponse } = await supertest
.get(`/api/fleet/agents?showInactive=true`)
.set('kbn-xsrf', 'xxxx')
diff --git a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts
index dff41ef2ead736..b8521914da1746 100644
--- a/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts
+++ b/x-pack/test/functional/apps/dashboard/group3/drilldowns/explore_data_chart_action.ts
@@ -13,7 +13,7 @@ const ACTION_TEST_SUBJ = `embeddablePanelAction-${ACTION_ID}`;
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const drilldowns = getService('dashboardDrilldownsManage');
- const { dashboard, discover, common, timePicker } = getPageObjects([
+ const { dashboard, discover, timePicker } = getPageObjects([
'dashboard',
'discover',
'common',
@@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('Explore underlying data - chart action', () => {
describe('value click action', () => {
it('action exists in chart click popup menu', async () => {
- await PageObjects.dashboard.navigateToApp();
+ await dashboard.navigateToApp();
await dashboard.preserveCrossAppState();
await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_PIE_CHART_NAME);
await pieChart.clickOnPieSlice('160,000');
@@ -60,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
let originalTimeRangeDurationHours: number | undefined;
it('action exists in chart brush popup menu', async () => {
- await PageObjects.dashboard.navigateToApp();
+ await dashboard.navigateToApp();
await dashboard.preserveCrossAppState();
await dashboard.loadSavedDashboard(drilldowns.DASHBOARD_WITH_AREA_CHART_NAME);
diff --git a/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts b/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts
index d050a1a9013c66..4364e386b86372 100644
--- a/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts
+++ b/x-pack/test/functional/apps/transform/creation/index_pattern/continuous_transform.ts
@@ -6,6 +6,7 @@
*/
import { TRANSFORM_STATE } from '@kbn/transform-plugin/common/constants';
+import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
import type { FtrProviderContext } from '../../../../ftr_provider_context';
import {
diff --git a/x-pack/test/functional/page_objects/tag_management_page.ts b/x-pack/test/functional/page_objects/tag_management_page.ts
index 2dbff16429c93d..7e8fbd8346c1d0 100644
--- a/x-pack/test/functional/page_objects/tag_management_page.ts
+++ b/x-pack/test/functional/page_objects/tag_management_page.ts
@@ -239,7 +239,6 @@ class TagAssignmentFlyout extends FtrService {
*/
export class TagManagementPageObject extends FtrService {
private readonly testSubjects = this.ctx.getService('testSubjects');
- private readonly find = this.ctx.getService('find');
private readonly browser = this.ctx.getService('browser');
private readonly retry = this.ctx.getService('retry');
private readonly header = this.ctx.getPageObject('header');
diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts
index d8dad778fa03e6..e2db084b46276a 100644
--- a/x-pack/test/functional/services/ml/api.ts
+++ b/x-pack/test/functional/services/ml/api.ts
@@ -1504,9 +1504,8 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) {
async deleteIngestPipeline(modelId: string, usePrefix: boolean = true) {
log.debug(`Deleting ingest pipeline for trained model with id "${modelId}"`);
- const { body, status } = await esSupertest.delete(
- `/_ingest/pipeline/${usePrefix ? 'pipeline_' : ''}${modelId}`
- );
+ // const { body, status } =
+ await esSupertest.delete(`/_ingest/pipeline/${usePrefix ? 'pipeline_' : ''}${modelId}`);
// @todo
// this.assertResponseStatusCode(200, status, body);
From bb5439f4e1c3267625b1bc15210950ee2af63dfa Mon Sep 17 00:00:00 2001
From: mohamedhamed-ahmed
Date: Wed, 27 Sep 2023 22:18:13 +0200
Subject: [PATCH 06/22] [Log Explorer] Show Filter controls in compressed style
(#167402)
closes https://github.com/elastic/kibana/issues/166440
This PR applies compressed style to the control group container.
---
.../customizations/custom_dataset_filters.tsx | 39 +++++++++++++++++--
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugins/log_explorer/public/customizations/custom_dataset_filters.tsx b/x-pack/plugins/log_explorer/public/customizations/custom_dataset_filters.tsx
index 1f27f4a12b05ff..c315971fb23a5e 100644
--- a/x-pack/plugins/log_explorer/public/customizations/custom_dataset_filters.tsx
+++ b/x-pack/plugins/log_explorer/public/customizations/custom_dataset_filters.tsx
@@ -7,8 +7,8 @@
import React from 'react';
import { ControlGroupRenderer } from '@kbn/controls-plugin/public';
import { Query } from '@kbn/es-query';
-import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import { euiStyled } from '@kbn/kibana-react-plugin/common';
import { useControlPanels } from '../hooks/use_control_panels';
import { LogExplorerProfileStateService } from '../state_machines/log_explorer_profile';
@@ -42,8 +42,41 @@ const CustomDatasetFilters = ({
};
const ControlGroupContainer = euiStyled.div`
- .controlGroup {
- min-height: unset;
+[class*='options_list_popover_footer--OptionsListPopoverFooter'] {
+ display: none;
+}
+
+[data-test-subj='optionsListControl__sortingOptionsButton'] {
+ display: none;
+}
+
+[id^='control-popover'] .euiPopoverTitle {
+ display: none;
+}
+
+ .euiFlexGroup.controlGroup {
+ min-height: 32px;
+ }
+
+ .euiFormControlLayout.euiFormControlLayout--group.controlFrame__formControlLayout {
+ height: 32px;
+
+ & .euiFormLabel.controlFrame__formControlLayoutLabel {
+ padding: 8px !important;
+ }
+
+ .euiButtonEmpty.euiFilterButton {
+ height: 32px;
+ }
+ }
+
+ .euiText.errorEmbeddableCompact__button {
+ padding: 8px;
+
+ .euiLink {
+ display: flex;
+ gap: 8px;
+ }
}
`;
From 7b4e6a6775cca077177546939a14042eac840098 Mon Sep 17 00:00:00 2001
From: christineweng <18648970+christineweng@users.noreply.github.com>
Date: Wed, 27 Sep 2023 15:19:25 -0500
Subject: [PATCH 07/22] [Security Solution] Expandable flyout - clean up
loading states and no data messages (#166939)
## Summary
This PR cleaned up the loading and empty state of most components in the
new expandable alerts flyout:
1) Refactor `FlyoutLoading` component to be used across components
2) Updated unit tests to explicitly check no data messages
3) Changed loading spinner in smaller components to be skeleton text
4) Moved error/no data message to table
5) Added loading and/or error messages for:
1) Right panel -> about -> Rule description, note that rule preview is
disabled if no rule is found
![image](https://github.com/elastic/kibana/assets/18648970/11cb7926-ff86-4deb-a9d1-993463fad466)
2) Right panel -> about -> Alert reason, note that the alert reason
preview is disabled if no reason available
![image](https://github.com/elastic/kibana/assets/18648970/9674d81a-ca8b-4fea-981a-23c2cf3f3bfc)
3) Right panel -> Investigation -> Highlighted fields
![image](https://github.com/elastic/kibana/assets/18648970/d349251a-a027-409d-b94d-2792abe6543c)
4) Right panel -> Insights -> host and user preview
![image](https://github.com/elastic/kibana/assets/18648970/161340aa-4ca1-4205-9628-87206743f776)
5) Right panel -> Visualization -> analyzer preview (to match error in
analyzer graph)
![image](https://github.com/elastic/kibana/assets/18648970/73b0018d-1f97-4fa3-be4d-b55afeb047b7)
6) Right panel -> About -> show rule summary & show alert reason when
there is an error
- Very uncommon because the button should be disabled if data is not
available, adding as fall back
![image](https://github.com/elastic/kibana/assets/18648970/5088e9f8-bdd1-4e61-8878-dba756b44c32)
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../components/correlations_details.test.tsx | 15 ++-
.../left/components/correlations_details.tsx | 18 ++--
.../left/components/entities_details.test.tsx | 27 ++----
.../left/components/entities_details.tsx | 12 +--
.../components/investigation_guide.test.tsx | 26 +++--
.../left/components/investigation_guide.tsx | 65 +++++--------
.../components/prevalence_details.test.tsx | 40 ++------
.../left/components/prevalence_details.tsx | 34 ++-----
.../flyout/left/components/related_cases.tsx | 19 +---
.../left/components/response_details.test.tsx | 12 +--
.../left/components/response_details.tsx | 2 +-
.../public/flyout/left/components/test_ids.ts | 11 +--
.../threat_intelligence_details.tsx | 44 ++++-----
.../components/alert_reason_preview.test.tsx | 15 +++
.../components/alert_reason_preview.tsx | 7 +-
.../preview/components/rule_preview.test.tsx | 9 +-
.../preview/components/rule_preview.tsx | 16 +++-
.../components/analyzer_preview.test.tsx | 20 +++-
.../right/components/analyzer_preview.tsx | 53 ++++++----
.../analyzer_preview_container.test.tsx | 26 ++---
.../components/analyzer_preview_container.tsx | 42 ++++----
.../components/correlations_overview.test.tsx | 18 ++--
.../components/correlations_overview.tsx | 12 +--
.../right/components/description.test.tsx | 86 ++++++++++-------
.../flyout/right/components/description.tsx | 59 +++++++-----
.../components/entities_overview.test.tsx | 23 ++---
.../right/components/entities_overview.tsx | 12 +--
.../components/highlighted_fields.test.tsx | 9 +-
.../right/components/highlighted_fields.tsx | 19 ++--
.../components/host_entity_overview.test.tsx | 28 ++++++
.../right/components/host_entity_overview.tsx | 75 +++++++++------
.../components/investigation_guide.test.tsx | 26 +++--
.../right/components/investigation_guide.tsx | 48 ++++------
.../flyout/right/components/mitre_attack.tsx | 1 +
.../components/prevalence_overview.test.tsx | 20 ++--
.../right/components/prevalence_overview.tsx | 12 +--
.../flyout/right/components/reason.test.tsx | 12 +--
.../public/flyout/right/components/reason.tsx | 16 +++-
.../session_preview_container.test.tsx | 53 +++++-----
.../components/session_preview_container.tsx | 96 +++++++++----------
.../flyout/right/components/test_ids.ts | 19 ++--
.../threat_intelligence_overview.test.tsx | 4 +-
.../threat_intelligence_overview.tsx | 3 +-
.../components/user_entity_overview.test.tsx | 31 ++++++
.../right/components/user_entity_overview.tsx | 71 +++++++++-----
.../shared/components/expandable_panel.tsx | 16 ++--
.../shared/components/flyout_loading.test.tsx | 6 ++
.../shared/components/flyout_loading.tsx | 15 ++-
..._details_left_panel_session_view_tab.cy.ts | 11 +--
.../alert_details_left_panel_response_tab.ts | 7 +-
...ert_details_left_panel_session_view_tab.ts | 4 -
51 files changed, 702 insertions(+), 623 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx
index 2bbfa8f4ab7c50..c6efa418f90796 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx
@@ -20,7 +20,6 @@ import {
CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID,
CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID,
CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID,
- CORRELATIONS_DETAILS_NO_DATA_TEST_ID,
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID,
} from './test_ids';
import { useFetchRelatedAlertsBySession } from '../../shared/hooks/use_fetch_related_alerts_by_session';
@@ -53,12 +52,13 @@ const renderCorrelationDetails = () => {
);
};
-
const CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID =
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(
CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_SECTION_TEST_ID
);
+const NO_DATA_MESSAGE = 'No correlations data available.';
+
describe('CorrelationsDetails', () => {
beforeEach(() => {
jest.clearAllMocks();
@@ -102,14 +102,14 @@ describe('CorrelationsDetails', () => {
dataCount: 1,
});
- const { getByTestId, queryByTestId } = renderCorrelationDetails();
+ const { getByTestId, queryByText } = renderCorrelationDetails();
expect(getByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(CORRELATIONS_DETAILS_BY_SOURCE_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(CORRELATIONS_DETAILS_BY_SESSION_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(CORRELATIONS_DETAILS_CASES_SECTION_TABLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(CORRELATIONS_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should render no section and show error message if show values are false', () => {
@@ -125,7 +125,7 @@ describe('CorrelationsDetails', () => {
jest.mocked(useShowRelatedCases).mockReturnValue(false);
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
- const { getByTestId, queryByTestId } = renderCorrelationDetails();
+ const { getByText, queryByTestId } = renderCorrelationDetails();
expect(
queryByTestId(CORRELATIONS_DETAILS_BY_ANCESTRY_SECTION_TABLE_TEST_ID)
@@ -140,10 +140,7 @@ describe('CorrelationsDetails', () => {
expect(
queryByTestId(CORRELATIONS_DETAILS_SUPPRESSED_ALERTS_TITLE_TEST_ID)
).not.toBeInTheDocument();
- expect(getByTestId(CORRELATIONS_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(CORRELATIONS_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent(
- 'No correlations data available.'
- );
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
it('should render no section if values are null', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx
index f7def1d23ac98b..03020832365327 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.tsx
@@ -6,9 +6,9 @@
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
-import { CORRELATIONS_DETAILS_NO_DATA_TEST_ID } from './test_ids';
+import { CORRELATIONS_DETAILS_TEST_ID } from './test_ids';
import { RelatedAlertsBySession } from './related_alerts_by_session';
import { RelatedAlertsBySameSourceEvent } from './related_alerts_by_same_source_event';
import { RelatedCases } from './related_cases';
@@ -56,7 +56,7 @@ export const CorrelationsDetails: React.FC = () => {
showSuppressedAlerts;
return (
- <>
+
{canShowAtLeastOneInsight ? (
{showSuppressedAlerts && (
@@ -98,14 +98,12 @@ export const CorrelationsDetails: React.FC = () => {
)}
) : (
-
-
-
+
)}
- >
+
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx
index 2ecda17c128ba7..17a94e7d05c255 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.test.tsx
@@ -11,12 +11,7 @@ import '@testing-library/jest-dom';
import { LeftPanelContext } from '../context';
import { TestProviders } from '../../../common/mock';
import { EntitiesDetails } from './entities_details';
-import {
- ENTITIES_DETAILS_NO_DATA_TEST_ID,
- ENTITIES_DETAILS_TEST_ID,
- HOST_DETAILS_TEST_ID,
- USER_DETAILS_TEST_ID,
-} from './test_ids';
+import { ENTITIES_DETAILS_TEST_ID, HOST_DETAILS_TEST_ID, USER_DETAILS_TEST_ID } from './test_ids';
import { mockContextValue } from '../mocks/mock_context';
import { EXPANDABLE_PANEL_CONTENT_TEST_ID } from '../../shared/components/test_ids';
@@ -40,6 +35,8 @@ jest.mock('react-redux', () => {
const USER_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(USER_DETAILS_TEST_ID);
const HOST_TEST_ID = EXPANDABLE_PANEL_CONTENT_TEST_ID(HOST_DETAILS_TEST_ID);
+const NO_DATA_MESSAGE = 'Host and user information are unavailable for this alert.';
+
const renderEntitiesDetails = (contextValue: LeftPanelContext) =>
render(
@@ -51,11 +48,11 @@ const renderEntitiesDetails = (contextValue: LeftPanelContext) =>
describe('', () => {
it('renders entities details correctly', () => {
- const { getByTestId, queryByTestId } = renderEntitiesDetails(mockContextValue);
+ const { getByTestId, queryByText } = renderEntitiesDetails(mockContextValue);
expect(getByTestId(ENTITIES_DETAILS_TEST_ID)).toBeInTheDocument();
expect(getByTestId(USER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(HOST_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should render no data message if user name and host name are not available', () => {
@@ -64,11 +61,8 @@ describe('', () => {
getFieldsData: (fieldName: string) =>
fieldName === '@timestamp' ? ['2022-07-25T08:20:18.966Z'] : [],
};
- const { getByTestId, queryByTestId } = renderEntitiesDetails(contextValue);
- expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent(
- 'Host and user information are unavailable for this alert.'
- );
+ const { getByText, queryByTestId } = renderEntitiesDetails(contextValue);
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
expect(queryByTestId(USER_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(HOST_TEST_ID)).not.toBeInTheDocument();
});
@@ -87,11 +81,8 @@ describe('', () => {
}
},
};
- const { getByTestId, queryByTestId } = renderEntitiesDetails(contextValue);
- expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(ENTITIES_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent(
- 'Host and user information are unavailable for this alert.'
- );
+ const { getByText, queryByTestId } = renderEntitiesDetails(contextValue);
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
expect(queryByTestId(USER_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(HOST_TEST_ID)).not.toBeInTheDocument();
});
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx
index 78ee6485811620..5821e3bec17c80 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/entities_details.tsx
@@ -12,7 +12,7 @@ import { useLeftPanelContext } from '../context';
import { getField } from '../../shared/utils';
import { UserDetails } from './user_details';
import { HostDetails } from './host_details';
-import { ENTITIES_DETAILS_NO_DATA_TEST_ID, ENTITIES_DETAILS_TEST_ID } from './test_ids';
+import { ENTITIES_DETAILS_TEST_ID } from './test_ids';
export const ENTITIES_TAB_ID = 'entities-details';
@@ -45,12 +45,10 @@ export const EntitiesDetails: React.FC = () => {
)}
) : (
-
-
-
+
)}
>
);
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx
index 37d91600bbe5ef..f628fac332c6d3 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.test.tsx
@@ -10,15 +10,15 @@ import { render } from '@testing-library/react';
import { InvestigationGuide } from './investigation_guide';
import { LeftPanelContext } from '../context';
import { TestProviders } from '../../../common/mock';
-import {
- INVESTIGATION_GUIDE_LOADING_TEST_ID,
- INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
-} from './test_ids';
+import { INVESTIGATION_GUIDE_TEST_ID, INVESTIGATION_GUIDE_LOADING_TEST_ID } from './test_ids';
import { mockContextValue } from '../mocks/mock_context';
import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide';
jest.mock('../../shared/hooks/use_investigation_guide');
+const NO_DATA_TEXT =
+ "There's no investigation guide for this rule. Edit the rule's settingsExternal link(opens in a new tab or window) to add one.";
+
const renderInvestigationGuide = (context: LeftPanelContext = mockContextValue) => (
@@ -35,8 +35,10 @@ describe('', () => {
basicAlertData: { ruleId: 'ruleId' },
ruleNote: 'test note',
});
- const { queryByTestId } = render(renderInvestigationGuide());
- expect(queryByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+
+ const { queryByTestId, getByText, queryByText } = render(renderInvestigationGuide());
+ expect(getByText('test note')).toBeInTheDocument();
+ expect(queryByText(NO_DATA_TEXT)).not.toBeInTheDocument();
expect(queryByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).not.toBeInTheDocument();
});
@@ -54,10 +56,7 @@ describe('', () => {
ruleNote: 'test note',
});
const { getByTestId } = render(renderInvestigationGuide());
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent(
- `There’s no investigation guide for this rule. Edit the rule's settingsExternal link(opens in a new tab or window) to add one.`
- );
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_TEXT);
});
it('should render no data message when there is no rule note', () => {
@@ -66,10 +65,7 @@ describe('', () => {
ruleNote: undefined,
});
const { getByTestId } = render(renderInvestigationGuide());
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent(
- `There’s no investigation guide for this rule. Edit the rule's settingsExternal link(opens in a new tab or window) to add one.`
- );
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_TEXT);
});
it('should render no data message when useInvestigationGuide errors out', () => {
@@ -78,6 +74,6 @@ describe('', () => {
error: true,
});
const { getByTestId } = render(renderInvestigationGuide());
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_TEXT);
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx
index 1152e11df6ba62..4c6452093f5e81 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/investigation_guide.tsx
@@ -5,15 +5,13 @@
* 2.0.
*/
import React from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useInvestigationGuide } from '../../shared/hooks/use_investigation_guide';
import { useLeftPanelContext } from '../context';
-import {
- INVESTIGATION_GUIDE_LOADING_TEST_ID,
- INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
-} from './test_ids';
+import { INVESTIGATION_GUIDE_TEST_ID, INVESTIGATION_GUIDE_LOADING_TEST_ID } from './test_ids';
import { InvestigationGuideView } from '../../../common/components/event_details/investigation_guide_view';
+import { FlyoutLoading } from '../../shared/components/flyout_loading';
/**
* Investigation guide displayed in the left panel.
@@ -26,22 +24,11 @@ export const InvestigationGuide: React.FC = () => {
dataFormattedForFieldBrowser,
});
- if (loading) {
- return (
-
-
-
-
-
- );
- }
-
return (
- <>
- {!error && basicAlertData.ruleId && ruleNote ? (
+
+ {loading ? (
+
+ ) : !error && basicAlertData.ruleId && ruleNote ? (
{
showFullView={true}
/>
) : (
-
-
-
-
- ),
- }}
- />
-
+
+
+
+ ),
+ }}
+ />
)}
- >
+
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx
index c202d7eab97686..9a1181fe006411 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx
@@ -10,12 +10,10 @@ import React from 'react';
import { LeftPanelContext } from '../context';
import { PrevalenceDetails } from './prevalence_details';
import {
- PREVALENCE_DETAILS_LOADING_TEST_ID,
PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID,
- PREVALENCE_DETAILS_NO_DATA_TEST_ID,
PREVALENCE_DETAILS_TABLE_TEST_ID,
PREVALENCE_DETAILS_UPSELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID,
@@ -47,6 +45,8 @@ jest.mock('../../../common/hooks/use_license', () => {
};
});
+const NO_DATA_MESSAGE = 'No prevalence data available.';
+
const panelContextValue = {
eventId: 'event id',
indexName: 'indexName',
@@ -96,7 +96,7 @@ describe('PrevalenceDetails', () => {
],
});
- const { getByTestId, getAllByTestId, queryByTestId } = renderPrevalenceDetails();
+ const { getByTestId, getAllByTestId, queryByTestId, queryByText } = renderPrevalenceDetails();
expect(getByTestId(PREVALENCE_DETAILS_TABLE_TEST_ID)).toBeInTheDocument();
expect(getAllByTestId(PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID).length).toBeGreaterThan(1);
@@ -114,7 +114,7 @@ describe('PrevalenceDetails', () => {
getAllByTestId(PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID).length
).toBeGreaterThan(1);
expect(queryByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should render formatted numbers for the alert and document count columns', () => {
@@ -201,20 +201,6 @@ describe('PrevalenceDetails', () => {
expect(getByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).toBeInTheDocument();
});
- it('should render loading', () => {
- (usePrevalence as jest.Mock).mockReturnValue({
- loading: true,
- error: false,
- data: [],
- });
-
- const { getByTestId, queryByTestId } = renderPrevalenceDetails();
-
- expect(getByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(PREVALENCE_DETAILS_UPSELL_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).not.toBeInTheDocument();
- });
-
it('should render no data message if call errors out', () => {
(usePrevalence as jest.Mock).mockReturnValue({
loading: false,
@@ -222,13 +208,8 @@ describe('PrevalenceDetails', () => {
data: [],
});
- const { getByTestId, queryByTestId } = renderPrevalenceDetails();
-
- expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent(
- 'No prevalence data available.'
- );
- expect(queryByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).not.toBeInTheDocument();
+ const { getByText } = renderPrevalenceDetails();
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
it('should render no data message if no data', () => {
@@ -238,12 +219,7 @@ describe('PrevalenceDetails', () => {
data: [],
});
- const { getByTestId, queryByTestId } = renderPrevalenceDetails();
-
- expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(PREVALENCE_DETAILS_NO_DATA_TEST_ID)).toHaveTextContent(
- 'No prevalence data available.'
- );
- expect(queryByTestId(PREVALENCE_DETAILS_LOADING_TEST_ID)).not.toBeInTheDocument();
+ const { getByText } = renderPrevalenceDetails();
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx
index 4af7c2feeeb464..9ddae380c91362 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx
@@ -14,7 +14,6 @@ import {
EuiFlexItem,
EuiInMemoryTable,
EuiLink,
- EuiLoadingSpinner,
EuiPanel,
EuiSpacer,
EuiSuperDatePicker,
@@ -28,14 +27,12 @@ import { InvestigateInTimelineButton } from '../../../common/components/event_de
import type { PrevalenceData } from '../../shared/hooks/use_prevalence';
import { usePrevalence } from '../../shared/hooks/use_prevalence';
import {
- PREVALENCE_DETAILS_LOADING_TEST_ID,
PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID,
- PREVALENCE_DETAILS_NO_DATA_TEST_ID,
PREVALENCE_DETAILS_DATE_PICKER_TEST_ID,
PREVALENCE_DETAILS_TABLE_TEST_ID,
PREVALENCE_DETAILS_UPSELL_TEST_ID,
@@ -319,19 +316,6 @@ export const PrevalenceDetails: React.FC = () => {
[data, absoluteStart, absoluteEnd]
);
- if (loading) {
- return (
-
-
-
-
-
- );
- }
-
const upsell = (
<>
@@ -366,20 +350,18 @@ export const PrevalenceDetails: React.FC = () => {
width="full"
/>
- {data.length > 0 ? (
-
- ) : (
-
+
-
- )}
+ }
+ />
>
);
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx
index 681d542eac3a1b..54c96effd60e83 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/related_cases.tsx
@@ -7,10 +7,9 @@
import React from 'react';
import type { EuiBasicTableColumn } from '@elastic/eui';
-import { EuiInMemoryTable, EuiSkeletonText } from '@elastic/eui';
+import { EuiInMemoryTable } from '@elastic/eui';
import type { RelatedCase } from '@kbn/cases-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
-import { i18n } from '@kbn/i18n';
import { CellTooltipWrapper } from '../../shared/components/cell_tooltip_wrapper';
import { CaseDetailsLink } from '../../../common/components/links';
import {
@@ -65,22 +64,6 @@ export interface RelatedCasesProps {
export const RelatedCases: React.VFC = ({ eventId }) => {
const { loading, error, data, dataCount } = useFetchRelatedCases({ eventId });
- if (loading) {
- return (
-
- );
- }
-
if (error) {
return null;
}
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx
index cea5e630036622..dc6f3168eccadc 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.test.tsx
@@ -10,7 +10,7 @@ import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { LeftPanelContext } from '../context';
import { rawEventData, TestProviders } from '../../../common/mock';
-import { RESPONSE_DETAILS_TEST_ID, RESPONSE_NO_DATA_TEST_ID } from './test_ids';
+import { RESPONSE_DETAILS_TEST_ID } from './test_ids';
import { ResponseDetails } from './response_details';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
@@ -59,6 +59,9 @@ jest.mock('../../../common/lib/kibana', () => {
};
});
+const NO_DATA_MESSAGE =
+ "There are no response actions defined for this event. To add some, edit the rule's settings and set up response actionsExternal link(opens in a new tab or window).";
+
const defaultContextValue = {
dataAsNestedObject: {
_id: 'test',
@@ -114,7 +117,7 @@ describe('', () => {
expect(wrapper.getByTestId('responseActionsViewWrapper')).toBeInTheDocument();
expect(wrapper.queryByTestId('osqueryViewWrapper')).not.toBeInTheDocument();
- expect(wrapper.queryByTestId(RESPONSE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).not.toHaveTextContent(NO_DATA_MESSAGE);
});
it('should render the view with osquery only', () => {
@@ -135,9 +138,6 @@ describe('', () => {
expect(wrapper.queryByTestId('responseActionsViewWrapper')).not.toBeInTheDocument();
expect(wrapper.queryByTestId('osqueryViewWrapper')).not.toBeInTheDocument();
- expect(wrapper.getByTestId(RESPONSE_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(wrapper.getByTestId(RESPONSE_NO_DATA_TEST_ID)).toHaveTextContent(
- 'There are no response actions defined for this event. To add some, edit the rule’s settings and set up response actionsExternal link(opens in a new tab or window).'
- );
+ expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toHaveTextContent(NO_DATA_MESSAGE);
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx
index 9e813b518a9f61..06bb1096e3d988 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/response_details.tsx
@@ -69,7 +69,7 @@ export const ResponseDetails: React.FC = () => {
{
setRange,
} = useThreatIntelligenceDetails();
- if (isEventDataLoading) {
- return (
-
-
-
-
-
- );
- }
-
- return (
- <>
-
- <>
-
-
- >
-
- >
+ return isEventDataLoading ? (
+
+ ) : (
+
+ <>
+
+
+ >
+
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx
index 51feb8ce9d37ff..1408a5f6586306 100644
--- a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.test.tsx
@@ -21,6 +21,8 @@ const panelContextValue = {
...mockContextValue,
};
+const NO_DATA_MESSAGE = 'There was an error displaying data.';
+
describe('', () => {
it('should render alert reason preview', () => {
const { getByTestId } = render(
@@ -35,4 +37,17 @@ describe('', () => {
expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ALERT_REASON_PREVIEW_BODY_TEST_ID)).toHaveTextContent('Alert reason');
});
+
+ it('should render no data message if alert reason is not available', () => {
+ const { getByText } = render(
+
+
+
+
+
+
+
+ );
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx
index 476a1f3551948f..3702160339b063 100644
--- a/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/preview/components/alert_reason_preview.tsx
@@ -14,6 +14,7 @@ import { ALERT_REASON_PREVIEW_BODY_TEST_ID } from './test_ids';
import { usePreviewPanelContext } from '../context';
import { getRowRenderer } from '../../../timelines/components/timeline/body/renderers/get_row_renderer';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
+import { FlyoutError } from '../../shared/components/flyout_error';
const ReasonPreviewContainerWrapper = styled.div`
overflow-x: auto;
@@ -47,7 +48,11 @@ export const AlertReasonPreview: React.FC = () => {
);
if (!renderer) {
- return null;
+ return (
+
+
+
+ );
}
return (
diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx
index bc3f6c5e8f8e3e..9a76a852b5a924 100644
--- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.test.tsx
@@ -67,11 +67,9 @@ const renderRulePreview = () =>
);
-describe('', () => {
- beforeEach(() => {
- // (useAppToasts as jest.Mock).mockReturnValue(useAppToastsValueMock);
- });
+const NO_DATA_MESSAGE = 'There was an error displaying data.';
+describe('', () => {
afterEach(() => {
jest.clearAllMocks();
});
@@ -134,9 +132,10 @@ describe('', () => {
it('should not render rule preview when rule is null', async () => {
mockUseRuleWithFallback.mockReturnValue({});
- const { queryByTestId } = renderRulePreview();
+ const { queryByTestId, getByText } = renderRulePreview();
await act(async () => {
expect(queryByTestId(RULE_PREVIEW_BODY_TEST_ID)).not.toBeInTheDocument();
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx
index db87a9a681902c..84448aea0eb492 100644
--- a/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/preview/components/rule_preview.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
import React, { memo, useState, useEffect } from 'react';
-import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel, EuiLoadingSpinner } from '@elastic/eui';
+import { EuiText, EuiHorizontalRule, EuiSpacer, EuiPanel } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '../../../common/lib/kibana';
import { useGetSavedQuery } from '../../../detections/pages/detection_engine/rules/use_get_saved_query';
@@ -19,6 +19,8 @@ import { StepAboutRuleReadOnly } from '../../../detections/components/rules/step
import { StepDefineRuleReadOnly } from '../../../detections/components/rules/step_define_rule';
import { StepScheduleRuleReadOnly } from '../../../detections/components/rules/step_schedule_rule';
import { StepRuleActionsReadOnly } from '../../../detections/components/rules/step_rule_actions';
+import { FlyoutLoading } from '../../shared/components/flyout_loading';
+import { FlyoutError } from '../../shared/components/flyout_error';
import {
RULE_PREVIEW_BODY_TEST_ID,
RULE_PREVIEW_ABOUT_TEST_ID,
@@ -79,7 +81,9 @@ export const RulePreview: React.FC = memo(() => {
const hasResponseActions = Boolean(ruleActionsData?.responseActions?.length);
const hasActions = ruleActionsData != null && (hasNotificationActions || hasResponseActions);
- return rule ? (
+ return ruleLoading ? (
+
+ ) : rule ? (
@@ -169,9 +173,11 @@ export const RulePreview: React.FC = memo(() => {
)}
- ) : ruleLoading ? (
-
- ) : null;
+ ) : (
+
+
+
+ );
});
RulePreview.displayName = 'RulePreview';
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx
index 780bea57d1f096..dabc988aa2807f 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.test.tsx
@@ -30,6 +30,8 @@ const renderAnalyzerPreview = (contextValue: RightPanelContext) =>
);
+const NO_DATA_MESSAGE = 'An error is preventing this alert from being analyzed.';
+
describe('', () => {
beforeEach(() => {
jest.resetAllMocks();
@@ -57,7 +59,7 @@ describe('', () => {
expect(wrapper.getByTestId(ANALYZER_PREVIEW_TEST_ID)).toBeInTheDocument();
});
- it('does not show analyzer preview when documentId and index are not present', () => {
+ it('shows error message when documentid and index are not present', () => {
mockUseAlertPrevalenceFromProcessTree.mockReturnValue({
loading: false,
error: false,
@@ -76,13 +78,25 @@ describe('', () => {
},
],
};
- const { queryByTestId } = renderAnalyzerPreview(contextValue);
+ const { getByText } = renderAnalyzerPreview(contextValue);
expect(mockUseAlertPrevalenceFromProcessTree).toHaveBeenCalledWith({
isActiveTimeline: false,
documentId: '',
indices: [],
});
- expect(queryByTestId(ANALYZER_PREVIEW_TEST_ID)).not.toBeInTheDocument();
+
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
+ });
+
+ it('shows error message when there is an error', () => {
+ mockUseAlertPrevalenceFromProcessTree.mockReturnValue({
+ loading: false,
+ error: true,
+ alertIds: undefined,
+ statsNodes: undefined,
+ });
+ const { getByText } = renderAnalyzerPreview(mockContextValue);
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx
index 42749285de6125..5d4a21fca293b8 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview.tsx
@@ -6,9 +6,10 @@
*/
import React, { useEffect, useMemo, useState } from 'react';
import { find } from 'lodash/fp';
-import { EuiTreeView } from '@elastic/eui';
+import { EuiTreeView, EuiSkeletonText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { ANALYZER_PREVIEW_TEST_ID, ANALYZER_PREVIEW_LOADING_TEST_ID } from './test_ids';
import { getTreeNodes } from '../utils/analyzer_helpers';
import { ANCESTOR_ID, RULE_INDICES } from '../../shared/constants/field_names';
import { useRightPanelContext } from '../context';
@@ -41,7 +42,7 @@ export const AnalyzerPreview: React.FC = () => {
const index = find({ category: 'kibana', field: RULE_INDICES }, data);
const indices = index?.values ?? [];
- const { statsNodes } = useAlertPrevalenceFromProcessTree({
+ const { statsNodes, loading, error } = useAlertPrevalenceFromProcessTree({
isActiveTimeline: isActiveTimeline(scopeId),
documentId: processDocumentId,
indices,
@@ -58,24 +59,36 @@ export const AnalyzerPreview: React.FC = () => {
[cache.statsNodes]
);
- if (!documentId || !index || !items || items.length === 0) {
- return null;
- }
+ const showAnalyzerTree = documentId && index && items && items.length > 0 && !error;
- return (
-
-
-
+ return loading ? (
+
+ ) : showAnalyzerTree ? (
+
+ ) : (
+
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx
index 60e17a0bf8433b..2adbf4e01d7050 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.test.tsx
@@ -9,9 +9,10 @@ import { render, screen } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import React from 'react';
import { RightPanelContext } from '../context';
+import { mockContextValue } from '../mocks/mock_context';
import { AnalyzerPreviewContainer } from './analyzer_preview_container';
import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
-import { ANALYZER_PREVIEW_NO_DATA_TEST_ID, ANALYZER_PREVIEW_TEST_ID } from './test_ids';
+import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { useAlertPrevalenceFromProcessTree } from '../../../common/containers/alerts/use_alert_prevalence_from_process_tree';
import * as mock from '../mocks/mock_analyzer_data';
import {
@@ -42,9 +43,13 @@ jest.mock('react-redux', () => {
};
});
+const NO_ANALYZER_MESSAGE =
+ 'You can only visualize events triggered by hosts configured with the Elastic Defend integration or any sysmon data from winlogbeat. Refer to Visual event analyzerExternal link(opens in a new tab or window) for more information.';
+
const panelContextValue = {
+ ...mockContextValue,
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
-} as unknown as RightPanelContext;
+};
const renderAnalyzerPreview = () =>
render(
@@ -72,10 +77,9 @@ describe('AnalyzerPreviewContainer', () => {
investigateInTimelineAlertClick: jest.fn(),
});
- const { getByTestId, queryByTestId } = renderAnalyzerPreview();
+ const { getByTestId } = renderAnalyzerPreview();
expect(getByTestId(ANALYZER_PREVIEW_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(ANALYZER_PREVIEW_NO_DATA_TEST_ID)).not.toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
@@ -91,6 +95,9 @@ describe('AnalyzerPreviewContainer', () => {
expect(
screen.queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
+ expect(
+ screen.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
+ ).not.toHaveTextContent(NO_ANALYZER_MESSAGE);
});
it('should render error message and text in header', () => {
@@ -99,16 +106,13 @@ describe('AnalyzerPreviewContainer', () => {
investigateInTimelineAlertClick: jest.fn(),
});
- const { getByTestId, queryByTestId } = renderAnalyzerPreview();
-
- expect(queryByTestId(ANALYZER_PREVIEW_TEST_ID)).not.toBeInTheDocument();
- expect(getByTestId(ANALYZER_PREVIEW_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(ANALYZER_PREVIEW_NO_DATA_TEST_ID)).toHaveTextContent(
- 'You can only visualize events triggered by hosts configured with the Elastic Defend integration or any sysmon data from winlogbeat. Refer to Visual event analyzerExternal link(opens in a new tab or window) for more information.'
- );
+ const { getByTestId } = renderAnalyzerPreview();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
).toBeInTheDocument();
+ expect(
+ getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
+ ).toHaveTextContent(NO_ANALYZER_MESSAGE);
});
it('should navigate to left section Visualize tab when clicking on title', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx
index b059bcd1138d01..ed575481fabd91 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/analyzer_preview_container.tsx
@@ -18,7 +18,7 @@ import { setActiveTabTimeline } from '../../../timelines/store/timeline/actions'
import { useRightPanelContext } from '../context';
import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
import { AnalyzerPreview } from './analyzer_preview';
-import { ANALYZER_PREVIEW_NO_DATA_TEST_ID, ANALYZER_PREVIEW_TEST_ID } from './test_ids';
+import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
const timelineId = 'timeline-1';
@@ -81,27 +81,25 @@ export const AnalyzerPreviewContainer: React.FC = () => {
{isEnabled ? (
) : (
-
- {'sysmon'},
- winlogbeat: {'winlogbeat'},
- link: (
-
-
-
- ),
- }}
- />
-
+ {'sysmon'},
+ winlogbeat: {'winlogbeat'},
+ link: (
+
+
+
+ ),
+ }}
+ />
)}
);
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx
index ec94fffd9bbe02..9d1a73e5bd6160 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.test.tsx
@@ -14,7 +14,6 @@ import { CorrelationsOverview } from './correlations_overview';
import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details';
import { LeftPanelInsightsTab, LeftPanelKey } from '../../left';
import {
- CORRELATIONS_NO_DATA_TEST_ID,
CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID,
CORRELATIONS_RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID,
CORRELATIONS_RELATED_ALERTS_BY_SESSION_TEST_ID,
@@ -82,6 +81,8 @@ const renderCorrelationsOverview = (contextValue: RightPanelContext) => (
);
+const NO_DATA_MESSAGE = 'No correlations data available.';
+
describe('', () => {
it('should render wrapper component', () => {
jest.mocked(useShowRelatedAlertsByAncestry).mockReturnValue({ show: false });
@@ -131,13 +132,13 @@ describe('', () => {
dataCount: 1,
});
- const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
+ const { getByTestId, queryByText } = render(renderCorrelationsOverview(panelContextValue));
expect(getByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).toBeInTheDocument();
expect(getByTestId(RELATED_CASES_TEST_ID)).toBeInTheDocument();
expect(getByTestId(SUPPRESSED_ALERTS_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(CORRELATIONS_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should hide rows and show error message if show values are false', () => {
@@ -153,16 +154,13 @@ describe('', () => {
jest.mocked(useShowRelatedCases).mockReturnValue(false);
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
- const { getByTestId, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
+ const { getByText, queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument();
- expect(getByTestId(CORRELATIONS_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(CORRELATIONS_NO_DATA_TEST_ID)).toHaveTextContent(
- 'No correlations data available.'
- );
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
it('should hide rows if values are null', () => {
@@ -172,13 +170,13 @@ describe('', () => {
jest.mocked(useShowRelatedCases).mockReturnValue(false);
jest.mocked(useShowSuppressedAlerts).mockReturnValue({ show: false, alertSuppressionCount: 0 });
- const { queryByTestId } = render(renderCorrelationsOverview(panelContextValue));
+ const { queryByTestId, queryByText } = render(renderCorrelationsOverview(panelContextValue));
expect(queryByTestId(RELATED_ALERTS_BY_ANCESTRY_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_ALERTS_BY_SAME_SOURCE_EVENT_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_ALERTS_BY_SESSION_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(RELATED_CASES_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(SUPPRESSED_ALERTS_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(CORRELATIONS_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should navigate to the left section Insights tab when clicking on button', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx
index 1911d3a6fb5dda..84349b2b8e523c 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/correlations_overview.tsx
@@ -20,7 +20,7 @@ import { SuppressedAlerts } from './suppressed_alerts';
import { useShowSuppressedAlerts } from '../../shared/hooks/use_show_suppressed_alerts';
import { RelatedCases } from './related_cases';
import { useShowRelatedCases } from '../../shared/hooks/use_show_related_cases';
-import { CORRELATIONS_NO_DATA_TEST_ID, CORRELATIONS_TEST_ID } from './test_ids';
+import { CORRELATIONS_TEST_ID } from './test_ids';
import { useRightPanelContext } from '../context';
import { LeftPanelKey, LeftPanelInsightsTab } from '../../left';
import { CORRELATIONS_TAB_ID } from '../../left/components/correlations_details';
@@ -120,12 +120,10 @@ export const CorrelationsOverview: React.FC = () => {
)}
) : (
-
-
-
+
)}
);
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx
index 3dc09a7ac598f3..2cf9276a1853ee 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.test.tsx
@@ -8,7 +8,11 @@
import React from 'react';
import { FormattedMessage, __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { render } from '@testing-library/react';
-import { DESCRIPTION_TITLE_TEST_ID, RULE_SUMMARY_BUTTON_TEST_ID } from './test_ids';
+import {
+ DESCRIPTION_TITLE_TEST_ID,
+ RULE_SUMMARY_BUTTON_TEST_ID,
+ DESCRIPTION_DETAILS_TEST_ID,
+} from './test_ids';
import { Description } from './description';
import { RightPanelContext } from '../context';
import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
@@ -64,6 +68,8 @@ const renderDescription = (panelContext: RightPanelContext) =>
);
+const NO_DATA_MESSAGE = "There's no description for this rule.";
+
describe('', () => {
it('should render the component', () => {
const { getByTestId } = renderDescription(
@@ -72,17 +78,15 @@ describe('', () => {
expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent('Rule description');
+ expect(getByTestId(DESCRIPTION_DETAILS_TEST_ID)).toHaveTextContent(ruleDescription.values[0]);
expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toBeInTheDocument();
});
- it('should not render rule preview button if rule name is not available', () => {
- const { getByTestId, queryByTestId } = renderDescription(
- panelContextValue([ruleUuid, ruleDescription])
- );
+ it('should render no data message if rule description is not available', () => {
+ const { getByTestId, getByText } = renderDescription(panelContextValue([ruleUuid]));
- expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent('Rule description');
- expect(queryByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(DESCRIPTION_DETAILS_TEST_ID)).toBeInTheDocument();
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
it('should render document title if document is not an alert', () => {
@@ -92,32 +96,48 @@ describe('', () => {
expect(getByTestId(DESCRIPTION_TITLE_TEST_ID)).toHaveTextContent('Document description');
});
- it('should open preview panel when clicking on button', () => {
- const panelContext = panelContextValue([ruleUuid, ruleDescription, ruleName]);
-
- const { getByTestId } = renderDescription(panelContext);
-
- getByTestId(RULE_SUMMARY_BUTTON_TEST_ID).click();
-
- expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({
- id: PreviewPanelKey,
- path: { tab: 'rule-preview' },
- params: {
- id: panelContext.eventId,
- indexName: panelContext.indexName,
- scopeId: panelContext.scopeId,
- banner: {
- title: (
-
- ),
- backgroundColor: 'warning',
- textColor: 'warning',
+ describe('rule preview', () => {
+ it('should render rule preview button as disabled if rule name is not available', () => {
+ const { getByTestId } = renderDescription(panelContextValue([ruleUuid, ruleDescription]));
+ expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toHaveAttribute('disabled');
+ });
+
+ it('should render rule preview button as disabled if rule id is not available', () => {
+ const { getByTestId } = renderDescription(
+ panelContextValue([{ ...ruleUuid, values: [] }, ruleName, ruleDescription])
+ );
+ expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toHaveAttribute('disabled');
+ });
+
+ it('should open preview panel when clicking on button', () => {
+ const panelContext = panelContextValue([ruleUuid, ruleDescription, ruleName]);
+
+ const { getByTestId } = renderDescription(panelContext);
+
+ getByTestId(RULE_SUMMARY_BUTTON_TEST_ID).click();
+
+ expect(flyoutContextValue.openPreviewPanel).toHaveBeenCalledWith({
+ id: PreviewPanelKey,
+ path: { tab: 'rule-preview' },
+ params: {
+ id: panelContext.eventId,
+ indexName: panelContext.indexName,
+ scopeId: panelContext.scopeId,
+ banner: {
+ title: (
+
+ ),
+ backgroundColor: 'warning',
+ textColor: 'warning',
+ },
+ ruleId: ruleUuid.values[0],
},
- ruleId: ruleUuid.values[0],
- },
+ });
});
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx
index 4b0169eeacbc22..d180d58db2a228 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/description.tsx
@@ -56,34 +56,41 @@ export const Description: FC = () => {
}, [eventId, openPreviewPanel, indexName, scopeId, ruleId]);
const viewRule = useMemo(
- () =>
- !isEmpty(ruleName) &&
- !isEmpty(ruleId) && (
-
-
-
-
-
- ),
+ () => (
+
+
+
+
+
+ ),
[ruleName, openRulePreview, ruleId]
);
- const hasRuleDescription = ruleDescription && ruleDescription.length > 0;
+ const alertRuleDescription =
+ ruleDescription?.length > 0 ? (
+ ruleDescription
+ ) : (
+
+ );
return (
@@ -112,7 +119,7 @@ export const Description: FC = () => {
- {hasRuleDescription ? ruleDescription : '-'}
+ {isAlert ? alertRuleDescription : '-'}
);
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx
index ea66a0da11f171..877d4053622bbb 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.test.tsx
@@ -11,7 +11,6 @@ import { RightPanelContext } from '../context';
import {
ENTITIES_HOST_OVERVIEW_TEST_ID,
ENTITIES_USER_OVERVIEW_TEST_ID,
- INSIGHTS_ENTITIES_NO_DATA_TEST_ID,
INSIGHTS_ENTITIES_TEST_ID,
} from './test_ids';
import { EntitiesOverview } from './entities_overview';
@@ -45,6 +44,8 @@ const renderEntitiesOverview = (contextValue: RightPanelContext) =>
);
+const NO_DATA_MESSAGE = 'Host and user information are unavailable for this alert.';
+
describe('', () => {
it('should render wrapper component', () => {
const { getByTestId, queryByTestId } = renderEntitiesOverview(mockContextValue);
@@ -57,10 +58,10 @@ describe('', () => {
});
it('should render user and host', () => {
- const { getByTestId, queryByTestId } = renderEntitiesOverview(mockContextValue);
+ const { getByTestId, queryByText } = renderEntitiesOverview(mockContextValue);
expect(getByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).toBeInTheDocument();
expect(getByTestId(ENTITIES_HOST_OVERVIEW_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should only render user when host name is null', () => {
@@ -69,11 +70,11 @@ describe('', () => {
getFieldsData: (field: string) => (field === 'user.name' ? 'user1' : null),
} as unknown as RightPanelContext;
- const { queryByTestId, getByTestId } = renderEntitiesOverview(contextValue);
+ const { queryByTestId, getByTestId, queryByText } = renderEntitiesOverview(contextValue);
expect(getByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(ENTITIES_HOST_OVERVIEW_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should only render host when user name is null', () => {
@@ -82,11 +83,11 @@ describe('', () => {
getFieldsData: (field: string) => (field === 'host.name' ? 'host1' : null),
} as unknown as RightPanelContext;
- const { queryByTestId, getByTestId } = renderEntitiesOverview(contextValue);
+ const { queryByTestId, getByTestId, queryByText } = renderEntitiesOverview(contextValue);
expect(getByTestId(ENTITIES_HOST_OVERVIEW_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(ENTITIES_USER_OVERVIEW_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should render no data message if both host name and user name are null/blank', () => {
@@ -95,11 +96,7 @@ describe('', () => {
getFieldsData: (field: string) => {},
} as unknown as RightPanelContext;
- const { queryByTestId } = renderEntitiesOverview(contextValue);
-
- expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(INSIGHTS_ENTITIES_NO_DATA_TEST_ID)).toHaveTextContent(
- 'Host and user information are unavailable for this alert.'
- );
+ const { getByText } = renderEntitiesOverview(contextValue);
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx
index 52ac44137a1d64..38d9a25437e812 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/entities_overview.tsx
@@ -9,7 +9,7 @@ import React, { useCallback } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { FormattedMessage } from '@kbn/i18n-react';
-import { INSIGHTS_ENTITIES_NO_DATA_TEST_ID, INSIGHTS_ENTITIES_TEST_ID } from './test_ids';
+import { INSIGHTS_ENTITIES_TEST_ID } from './test_ids';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
import { useRightPanelContext } from '../context';
import { getField } from '../../shared/utils';
@@ -80,12 +80,10 @@ export const EntitiesOverview: React.FC = () => {
)}
) : (
-
-
-
+
)}
>
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx
index 4f4c750c8d39c0..47754818704dd7 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx
@@ -27,6 +27,8 @@ const renderHighlightedFields = (contextValue: RightPanelContext) =>
);
+const NO_DATA_MESSAGE = "There's no highlighted fields for this alert.";
+
describe('', () => {
beforeEach(() => {
(useRuleWithFallback as jest.Mock).mockReturnValue({ investigation_fields: undefined });
@@ -49,15 +51,14 @@ describe('', () => {
expect(getByTestId(HIGHLIGHTED_FIELDS_DETAILS_TEST_ID)).toBeInTheDocument();
});
- it(`should render empty component if there aren't any highlighted fields`, () => {
+ it(`should render no data message if there aren't any highlighted fields`, () => {
const contextValue = {
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
scopeId: 'scopeId',
} as unknown as RightPanelContext;
(useHighlightedFields as jest.Mock).mockReturnValue({});
- const { container } = renderHighlightedFields(contextValue);
-
- expect(container).toBeEmptyDOMElement();
+ const { getByText } = renderHighlightedFields(contextValue);
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx
index d47ab0c4b3c02f..48657615755b6b 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx
@@ -95,7 +95,7 @@ const columns: Array> = [
export const HighlightedFields: FC = () => {
const { dataFormattedForFieldBrowser, scopeId } = useRightPanelContext();
const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
- const { rule: maybeRule } = useRuleWithFallback(ruleId);
+ const { loading, error, rule: maybeRule } = useRuleWithFallback(ruleId);
const highlightedFields = useHighlightedFields({
dataFormattedForFieldBrowser,
@@ -106,10 +106,6 @@ export const HighlightedFields: FC = () => {
[highlightedFields, scopeId]
);
- if (items.length === 0) {
- return null;
- }
-
return (
@@ -124,7 +120,18 @@ export const HighlightedFields: FC = () => {
-
+
+ }
+ />
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx
index 8680120dfd2513..c5a9f7d7d324ae 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.test.tsx
@@ -16,6 +16,7 @@ import {
ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID,
ENTITIES_HOST_OVERVIEW_LINK_TEST_ID,
ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID,
+ ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID,
} from './test_ids';
import { RightPanelContext } from '../context';
import { mockContextValue } from '../mocks/mock_context';
@@ -100,6 +101,33 @@ describe('', () => {
});
});
+ it('should render loading if loading for host details is true', () => {
+ mockUseHostDetails.mockReturnValue([true, { hostDetails: null }]);
+ mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true });
+
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+ expect(getByTestId(ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID)).toBeInTheDocument();
+ });
+
+ it('should render loading if loading for risk score is true', () => {
+ mockUseHostDetails.mockReturnValue([false, { hostDetails: null }]);
+ mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true, loading: true });
+
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+ expect(getByTestId(ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID)).toBeInTheDocument();
+ });
describe('license is not valid', () => {
it('should render os family and last seen', () => {
mockUseHostDetails.mockReturnValue([false, { hostDetails: hostData }]);
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx
index 60d045006ac496..36f1bec0a8f5be 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/host_entity_overview.tsx
@@ -14,9 +14,11 @@ import {
useEuiTheme,
useEuiFontSize,
EuiIconTip,
+ EuiSkeletonText,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { getOr } from 'lodash/fp';
+import { i18n } from '@kbn/i18n';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { FormattedMessage } from '@kbn/i18n-react';
import { useRightPanelContext } from '../context';
@@ -35,7 +37,11 @@ import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { useGlobalTime } from '../../../common/containers/use_global_time';
import { useRiskScore } from '../../../explore/containers/risk_score';
import { useHostDetails } from '../../../explore/hosts/containers/hosts/details';
-import * as i18n from '../../../overview/components/host_overview/translations';
+import {
+ FAMILY,
+ LAST_SEEN,
+ HOST_RISK_CLASSIFICATION,
+} from '../../../overview/components/host_overview/translations';
import { ENTITIES_TAB_ID } from '../../left/components/entities_details';
import {
ENTITIES_HOST_OVERVIEW_TEST_ID,
@@ -43,6 +49,7 @@ import {
ENTITIES_HOST_OVERVIEW_LAST_SEEN_TEST_ID,
ENTITIES_HOST_OVERVIEW_RISK_LEVEL_TEST_ID,
ENTITIES_HOST_OVERVIEW_LINK_TEST_ID,
+ ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID,
TECHNICAL_PREVIEW_ICON_TEST_ID,
} from './test_ids';
import { LeftPanelInsightsTab, LeftPanelKey } from '../../left';
@@ -91,14 +98,18 @@ export const HostEntityOverview: React.FC = ({ hostName
[hostName]
);
- const { data: hostRisk, isAuthorized } = useRiskScore({
+ const {
+ data: hostRisk,
+ isAuthorized,
+ loading: isRiskScoreLoading,
+ } = useRiskScore({
filterQuery,
riskEntity: RiskScoreEntity.host,
skip: hostName == null,
timerange,
});
- const [_, { hostDetails }] = useHostDetails({
+ const [isHostDetailsLoading, { hostDetails }] = useHostDetails({
hostName,
indexNames: selectedPatterns,
startDate: from,
@@ -108,7 +119,7 @@ export const HostEntityOverview: React.FC = ({ hostName
const hostOSFamily: DescriptionList[] = useMemo(
() => [
{
- title: i18n.FAMILY,
+ title: FAMILY,
description: (
= ({ hostName
const hostLastSeen: DescriptionList[] = useMemo(
() => [
{
- title: i18n.LAST_SEEN,
+ title: LAST_SEEN,
description: (
= ({ hostName
{
title: (
<>
- {i18n.HOST_RISK_CLASSIFICATION}
+ {HOST_RISK_CLASSIFICATION}
= ({ hostName
-
-
-
-
-
-
- {isAuthorized ? (
-
- ) : (
+ {isRiskScoreLoading || isHostDetailsLoading ? (
+
+ ) : (
+
+
+
- )}
-
-
-
+
+
+ {isAuthorized ? (
+
+ ) : (
+
+ )}
+
+
+
+ )}
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx
index 0dae715262fcc5..1dd3af16ff4159 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.test.tsx
@@ -13,7 +13,6 @@ import { RightPanelContext } from '../context';
import {
INVESTIGATION_GUIDE_BUTTON_TEST_ID,
INVESTIGATION_GUIDE_LOADING_TEST_ID,
- INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
INVESTIGATION_GUIDE_TEST_ID,
} from './test_ids';
import { mockContextValue } from '../mocks/mock_context';
@@ -24,6 +23,8 @@ import { LeftPanelInvestigationTab, LeftPanelKey } from '../../left';
jest.mock('../../shared/hooks/use_investigation_guide');
+const NO_DATA_MESSAGE = 'Investigation guideThere’s no investigation guide for this rule.';
+
const renderInvestigationGuide = () =>
render(
@@ -51,7 +52,7 @@ describe('', () => {
'Show investigation guide'
);
expect(queryByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).not.toHaveTextContent(NO_DATA_MESSAGE);
});
it('should render loading', () => {
@@ -59,10 +60,10 @@ describe('', () => {
loading: true,
});
const { getByTestId, queryByTestId } = renderInvestigationGuide();
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toBeInTheDocument();
expect(getByTestId(INVESTIGATION_GUIDE_LOADING_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(INVESTIGATION_GUIDE_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).not.toHaveTextContent(NO_DATA_MESSAGE);
});
it('should render no data message when there is no ruleId', () => {
@@ -70,12 +71,9 @@ describe('', () => {
basicAlertData: {},
ruleNote: 'test note',
});
- const { getByTestId, queryByTestId } = renderInvestigationGuide();
+ const { queryByTestId, getByTestId } = renderInvestigationGuide();
expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent(
- 'There’s no investigation guide for this rule.'
- );
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_MESSAGE);
});
it('should render no data message when there is no rule note', () => {
@@ -85,10 +83,7 @@ describe('', () => {
});
const { getByTestId, queryByTestId } = renderInvestigationGuide();
expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toHaveTextContent(
- 'There’s no investigation guide for this rule.'
- );
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_MESSAGE);
});
it('should render no data message when useInvestigationGuide errors out', () => {
@@ -97,8 +92,9 @@ describe('', () => {
error: true,
});
- const { getByTestId } = renderInvestigationGuide();
- expect(getByTestId(INVESTIGATION_GUIDE_NO_DATA_TEST_ID)).toBeInTheDocument();
+ const { queryByTestId, getByTestId } = renderInvestigationGuide();
+ expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument();
+ expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_MESSAGE);
});
it('should navigate to investigation guide when clicking on button', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx
index 05642d5425cc1b..d00310b360c261 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/investigation_guide.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useCallback } from 'react';
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiTitle } from '@elastic/eui';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSkeletonText } from '@elastic/eui';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
@@ -15,7 +15,6 @@ import { LeftPanelKey, LeftPanelInvestigationTab } from '../../left';
import {
INVESTIGATION_GUIDE_BUTTON_TEST_ID,
INVESTIGATION_GUIDE_LOADING_TEST_ID,
- INVESTIGATION_GUIDE_NO_DATA_TEST_ID,
INVESTIGATION_GUIDE_TEST_ID,
} from './test_ids';
@@ -45,22 +44,9 @@ export const InvestigationGuide: React.FC = () => {
});
}, [eventId, indexName, openLeftPanel, scopeId]);
- if (loading) {
- return (
-
-
-
-
-
- );
- }
-
return (
-
-
+
+
{
-
- {!error && basicAlertData.ruleId && ruleNote ? (
+ {loading ? (
+
+ ) : !error && basicAlertData.ruleId && ruleNote ? (
+
{
defaultMessage="Show investigation guide"
/>
- ) : (
-
-
-
- )}
-
+
+ ) : (
+
+ )}
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.tsx
index a4c02e2c3d2922..7eaf175cc0e5cd 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/mitre_attack.tsx
@@ -17,6 +17,7 @@ export const MitreAttack: FC = () => {
const threatDetails = useMemo(() => getMitreComponentParts(searchHit), [searchHit]);
if (!threatDetails || !threatDetails[0]) {
+ // Do not render empty message on MITRE attack because other frameworks could be used
return null;
}
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx
index 0f4d37ab01eb43..e8fc55e66288ac 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.test.tsx
@@ -9,7 +9,7 @@ import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
import { render } from '@testing-library/react';
import { TestProviders } from '../../../common/mock';
import { RightPanelContext } from '../context';
-import { PREVALENCE_NO_DATA_TEST_ID, PREVALENCE_TEST_ID } from './test_ids';
+import { PREVALENCE_TEST_ID } from './test_ids';
import { LeftPanelInsightsTab, LeftPanelKey } from '../../left';
import React from 'react';
import { PrevalenceOverview } from './prevalence_overview';
@@ -31,6 +31,8 @@ const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(PREVALENCE
const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(PREVALENCE_TEST_ID);
const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(PREVALENCE_TEST_ID);
+const NO_DATA_MESSAGE = 'No prevalence data available.';
+
const flyoutContextValue = {
openLeftPanel: jest.fn(),
} as unknown as ExpandableFlyoutContext;
@@ -69,10 +71,10 @@ describe('', () => {
data: [],
});
- const { getByTestId, queryByTestId } = renderPrevalenceOverview();
+ const { getByTestId, queryByText } = renderPrevalenceOverview();
expect(getByTestId(EXPANDABLE_PANEL_LOADING_TEST_ID(PREVALENCE_TEST_ID))).toBeInTheDocument();
- expect(queryByTestId(PREVALENCE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should render no-data message', () => {
@@ -82,12 +84,8 @@ describe('', () => {
data: [],
});
- const { getByTestId } = renderPrevalenceOverview();
-
- expect(getByTestId(PREVALENCE_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(PREVALENCE_NO_DATA_TEST_ID)).toHaveTextContent(
- 'No prevalence data available.'
- );
+ const { getByText } = renderPrevalenceOverview();
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
it('should render only data with prevalence less than 10%', () => {
@@ -116,7 +114,7 @@ describe('', () => {
],
});
- const { queryByTestId, getByTestId } = renderPrevalenceOverview();
+ const { queryByTestId, getByTestId, queryByText } = renderPrevalenceOverview();
expect(getByTestId(TITLE_LINK_TEST_ID)).toHaveTextContent('Prevalence');
@@ -131,7 +129,7 @@ describe('', () => {
expect(queryByTestId(iconDataTestSubj2)).not.toBeInTheDocument();
expect(queryByTestId(valueDataTestSubj2)).not.toBeInTheDocument();
- expect(queryByTestId(PREVALENCE_NO_DATA_TEST_ID)).not.toBeInTheDocument();
+ expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
it('should navigate to left section Insights tab when clicking on button', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx
index 36bb9c7c5b1690..233057a7245d94 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx
@@ -12,7 +12,7 @@ import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { FormattedMessage } from '@kbn/i18n-react';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
import { usePrevalence } from '../../shared/hooks/use_prevalence';
-import { PREVALENCE_NO_DATA_TEST_ID, PREVALENCE_TEST_ID } from './test_ids';
+import { PREVALENCE_TEST_ID } from './test_ids';
import { useRightPanelContext } from '../context';
import { LeftPanelKey, LeftPanelInsightsTab } from '../../left';
import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details';
@@ -107,12 +107,10 @@ export const PrevalenceOverview: FC = () => {
/>
))
) : (
-
-
-
+
)}
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx
index 200f4cf0536a01..f407c33a1e2106 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.test.tsx
@@ -8,11 +8,7 @@
import React from 'react';
import { render } from '@testing-library/react';
import { FormattedMessage, __IntlProvider as IntlProvider } from '@kbn/i18n-react';
-import {
- REASON_DETAILS_PREVIEW_BUTTON_TEST_ID,
- REASON_DETAILS_TEST_ID,
- REASON_TITLE_TEST_ID,
-} from './test_ids';
+import { REASON_DETAILS_PREVIEW_BUTTON_TEST_ID, REASON_TITLE_TEST_ID } from './test_ids';
import { Reason } from './reason';
import { RightPanelContext } from '../context';
import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
@@ -43,6 +39,8 @@ const renderReason = (panelContext: RightPanelContext = panelContextValue) =>
);
+const NO_DATA_MESSAGE = "There's no source event information for this alert.";
+
describe('', () => {
it('should render the component for alert', () => {
const { getByTestId } = renderReason();
@@ -73,9 +71,9 @@ describe('', () => {
getFieldsData: () => {},
} as unknown as RightPanelContext;
- const { getByTestId } = renderReason(panelContext);
+ const { getByText } = renderReason(panelContext);
- expect(getByTestId(REASON_DETAILS_TEST_ID)).toBeEmptyDOMElement();
+ expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});
it('should open preview panel when clicking on button', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx
index 2b44be75453a25..0d022f3a0735e5 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/reason.tsx
@@ -69,6 +69,7 @@ export const Reason: FC = () => {
defaultMessage: 'Show full reason',
}
)}
+ disabled={!alertReason}
>
{
),
- [openRulePreview]
+ [alertReason, openRulePreview]
+ );
+
+ const alertReasonText = alertReason ? (
+ alertReason
+ ) : (
+
);
return (
@@ -108,7 +118,9 @@ export const Reason: FC = () => {
- {alertReason}
+
+ {isAlert ? alertReasonText : '-'}
+
);
};
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx
index dc572207cd6023..ef0d52cead5fbf 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.test.tsx
@@ -12,11 +12,7 @@ import { RightPanelContext } from '../context';
import { SessionPreviewContainer } from './session_preview_container';
import { useSessionPreview } from '../hooks/use_session_preview';
import { useLicense } from '../../../common/hooks/use_license';
-import {
- SESSION_PREVIEW_NO_DATA_TEST_ID,
- SESSION_PREVIEW_TEST_ID,
- SESSION_PREVIEW_UPSELL_TEST_ID,
-} from './test_ids';
+import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import {
EXPANDABLE_PANEL_CONTENT_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
@@ -29,6 +25,11 @@ import { mockGetFieldsData } from '../../shared/mocks/mock_get_fields_data';
jest.mock('../hooks/use_session_preview');
jest.mock('../../../common/hooks/use_license');
+const NO_DATA_MESSAGE =
+ 'You can only view Linux session details if you’ve enabled the Include session data setting in your Elastic Defend integration policy. Refer to Enable Session View dataExternal link(opens in a new tab or window) for more information.';
+
+const UPSELL_TEXT = 'This feature requires an Enterprise subscription';
+
const panelContextValue = {
getFieldsData: mockGetFieldsData,
} as unknown as RightPanelContext;
@@ -57,14 +58,13 @@ describe('SessionPreviewContainer', () => {
(useSessionPreview as jest.Mock).mockReturnValue(sessionViewConfig);
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => true });
- const { getByTestId, queryByTestId } = renderSessionPreview();
+ const { getByTestId } = renderSessionPreview();
expect(getByTestId(SESSION_PREVIEW_TEST_ID)).toBeInTheDocument();
- expect(queryByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).not.toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
+
expect(
screen.queryByTestId(EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
@@ -77,6 +77,12 @@ describe('SessionPreviewContainer', () => {
expect(
screen.queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).not.toBeInTheDocument();
+ expect(
+ screen.queryByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
+ ).not.toHaveTextContent(NO_DATA_MESSAGE);
+ expect(
+ screen.queryByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
+ ).not.toHaveTextContent(UPSELL_TEXT);
});
it('should render error message and text in header if no sessionConfig', () => {
@@ -84,32 +90,35 @@ describe('SessionPreviewContainer', () => {
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => true });
const { getByTestId, queryByTestId } = renderSessionPreview();
-
- expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument();
- expect(getByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).toHaveTextContent(
- 'You can only view Linux session details if you’ve enabled the Include session data setting in your Elastic Defend integration policy. Refer to Enable Session View dataExternal link(opens in a new tab or window) for more information.'
- );
- expect(queryByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).not.toBeInTheDocument();
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
+ expect(
+ screen.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
+ ).toHaveTextContent(NO_DATA_MESSAGE);
+
+ expect(
+ screen.queryByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
+ ).not.toHaveTextContent(UPSELL_TEXT);
+ expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument();
});
- it('should render error message and text in header if no correct license', () => {
+ it('should render upsell message in header if no correct license', () => {
(useSessionPreview as jest.Mock).mockReturnValue(sessionViewConfig);
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => false });
const { getByTestId, queryByTestId } = renderSessionPreview();
- expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument();
- expect(queryByTestId(SESSION_PREVIEW_NO_DATA_TEST_ID)).not.toBeInTheDocument();
- expect(getByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).toBeInTheDocument();
- expect(getByTestId(SESSION_PREVIEW_UPSELL_TEST_ID)).toHaveTextContent(
- 'This feature requires an Enterprise subscription'
- );
expect(
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
).toBeInTheDocument();
+ expect(
+ screen.getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
+ ).toHaveTextContent(UPSELL_TEXT);
+
+ expect(
+ screen.queryByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
+ ).not.toHaveTextContent(NO_DATA_MESSAGE);
+ expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx
index 3fc3905ca6da97..f88d9d3f31a2c9 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/session_preview_container.tsx
@@ -18,11 +18,7 @@ import { useInvestigateInTimeline } from '../../../detections/components/alerts_
import { useRightPanelContext } from '../context';
import { ALERTS_ACTIONS } from '../../../common/lib/apm/user_actions';
import { ExpandablePanel } from '../../shared/components/expandable_panel';
-import {
- SESSION_PREVIEW_NO_DATA_TEST_ID,
- SESSION_PREVIEW_TEST_ID,
- SESSION_PREVIEW_UPSELL_TEST_ID,
-} from './test_ids';
+import { SESSION_PREVIEW_TEST_ID } from './test_ids';
import { useStartTransaction } from '../../../common/lib/apm/use_start_transaction';
import { setActiveTabTimeline } from '../../../timelines/store/timeline/actions';
import { getScopedActions } from '../../../helpers';
@@ -70,54 +66,50 @@ export const SessionPreviewContainer: FC = () => {
const { euiTheme } = useEuiTheme();
const noSessionMessage = !isEnterprisePlus ? (
-
-
-
-
- ),
- }}
- />
-
+
+
+
+ ),
+ }}
+ />
) : !sessionViewConfig ? (
-
-
-
-
- ),
- link: (
-
-
-
- ),
- }}
- />
-
+
+
+
+ ),
+ link: (
+
+
+
+ ),
+ }}
+ />
) : null;
return (
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts
index 8d1b13c12c1467..acff67542b0f4c 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/test_ids.ts
@@ -28,14 +28,17 @@ export const CHAT_BUTTON_TEST_ID = 'newChatById' as const;
export const ABOUT_SECTION_TEST_ID = `${PREFIX}AboutSection` as const;
export const ABOUT_SECTION_HEADER_TEST_ID = ABOUT_SECTION_TEST_ID + HEADER_TEST_ID;
export const ABOUT_SECTION_CONTENT_TEST_ID = ABOUT_SECTION_TEST_ID + CONTENT_TEST_ID;
+
export const RULE_SUMMARY_BUTTON_TEST_ID = `${PREFIX}RuleSummaryButton` as const;
const DESCRIPTION_TEST_ID = `${PREFIX}Description` as const;
export const DESCRIPTION_TITLE_TEST_ID = `${DESCRIPTION_TEST_ID}Title` as const;
export const DESCRIPTION_DETAILS_TEST_ID = `${DESCRIPTION_TEST_ID}Details` as const;
+
const REASON_TEST_ID = `${PREFIX}Reason` as const;
export const REASON_TITLE_TEST_ID = `${REASON_TEST_ID}Title` as const;
export const REASON_DETAILS_TEST_ID = `${REASON_TEST_ID}Details` as const;
export const REASON_DETAILS_PREVIEW_BUTTON_TEST_ID = `${REASON_TEST_ID}PreviewButton` as const;
+
const MITRE_ATTACK_TEST_ID = `${PREFIX}MitreAttack` as const;
export const MITRE_ATTACK_TITLE_TEST_ID = `${MITRE_ATTACK_TEST_ID}Title` as const;
export const MITRE_ATTACK_DETAILS_TEST_ID = `${MITRE_ATTACK_TEST_ID}Details` as const;
@@ -46,10 +49,11 @@ export const INVESTIGATION_SECTION_TEST_ID = `${PREFIX}InvestigationSection` as
export const INVESTIGATION_SECTION_HEADER_TEST_ID = INVESTIGATION_SECTION_TEST_ID + HEADER_TEST_ID;
export const INVESTIGATION_SECTION_CONTENT_TEST_ID =
INVESTIGATION_SECTION_TEST_ID + CONTENT_TEST_ID;
+
export const INVESTIGATION_GUIDE_TEST_ID = `${PREFIX}InvestigationGuide` as const;
export const INVESTIGATION_GUIDE_BUTTON_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Button` as const;
export const INVESTIGATION_GUIDE_LOADING_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}Loading` as const;
-export const INVESTIGATION_GUIDE_NO_DATA_TEST_ID = `${INVESTIGATION_GUIDE_TEST_ID}NoData` as const;
+
const HIGHLIGHTED_FIELDS_TEST_ID = `${PREFIX}HighlightedFields` as const;
export const HIGHLIGHTED_FIELDS_TITLE_TEST_ID = `${HIGHLIGHTED_FIELDS_TEST_ID}Title` as const;
export const HIGHLIGHTED_FIELDS_DETAILS_TEST_ID = `${HIGHLIGHTED_FIELDS_TEST_ID}Details` as const;
@@ -75,8 +79,10 @@ export const SUMMARY_ROW_VALUE_TEST_ID = (dataTestSubj: string) => `${dataTestSu
/* Entities */
export const INSIGHTS_ENTITIES_TEST_ID = `${PREFIX}InsightsEntities` as const;
-export const INSIGHTS_ENTITIES_NO_DATA_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}NoData` as const;
+
export const ENTITIES_USER_OVERVIEW_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}UserOverview` as const;
+export const ENTITIES_USER_OVERVIEW_LOADING_TEST_ID =
+ `${ENTITIES_USER_OVERVIEW_TEST_ID}Loading` as const;
export const ENTITIES_USER_OVERVIEW_LINK_TEST_ID = `${ENTITIES_USER_OVERVIEW_TEST_ID}Link` as const;
export const ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID =
`${ENTITIES_USER_OVERVIEW_TEST_ID}Domain` as const;
@@ -84,7 +90,10 @@ export const ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID =
`${ENTITIES_USER_OVERVIEW_TEST_ID}LastSeen` as const;
export const ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID =
`${ENTITIES_USER_OVERVIEW_TEST_ID}RiskLevel` as const;
+
export const ENTITIES_HOST_OVERVIEW_TEST_ID = `${INSIGHTS_ENTITIES_TEST_ID}HostOverview` as const;
+export const ENTITIES_HOST_OVERVIEW_LOADING_TEST_ID =
+ `${ENTITIES_HOST_OVERVIEW_TEST_ID}Loading` as const;
export const ENTITIES_HOST_OVERVIEW_LINK_TEST_ID = `${ENTITIES_HOST_OVERVIEW_TEST_ID}Link` as const;
export const ENTITIES_HOST_OVERVIEW_OS_FAMILY_TEST_ID =
`${ENTITIES_HOST_OVERVIEW_TEST_ID}OsFamily` as const;
@@ -102,7 +111,6 @@ export const INSIGHTS_THREAT_INTELLIGENCE_TEST_ID = `${PREFIX}InsightsThreatInte
/* Correlations */
export const CORRELATIONS_TEST_ID = `${PREFIX}Correlations` as const;
-export const CORRELATIONS_NO_DATA_TEST_ID = `${CORRELATIONS_TEST_ID}NoData` as const;
export const CORRELATIONS_SUPPRESSED_ALERTS_TEST_ID =
`${CORRELATIONS_TEST_ID}SuppressedAlerts` as const;
export const CORRELATIONS_SUPPRESSED_ALERTS_TECHNICAL_PREVIEW_TEST_ID =
@@ -118,7 +126,6 @@ export const CORRELATIONS_RELATED_ALERTS_BY_ANCESTRY_TEST_ID =
/* Insights Prevalence */
export const PREVALENCE_TEST_ID = `${PREFIX}InsightsPrevalence` as const;
-export const PREVALENCE_NO_DATA_TEST_ID = `${PREVALENCE_TEST_ID}NoData` as const;
/* Visualizations section */
@@ -127,10 +134,10 @@ export const VISUALIZATIONS_SECTION_TEST_ID = `${VISUALIZATIONS_TEST_ID}Title` a
export const VISUALIZATIONS_SECTION_HEADER_TEST_ID =
`${VISUALIZATIONS_TEST_ID}TitleHeader` as const;
export const ANALYZER_PREVIEW_TEST_ID = `${PREFIX}AnalyzerPreview` as const;
-export const ANALYZER_PREVIEW_NO_DATA_TEST_ID = `${ANALYZER_PREVIEW_TEST_ID}NoData` as const;
+export const ANALYZER_PREVIEW_LOADING_TEST_ID = `${ANALYZER_PREVIEW_TEST_ID}Loading` as const;
+
export const SESSION_PREVIEW_TEST_ID = `${PREFIX}SessionPreview` as const;
export const SESSION_PREVIEW_UPSELL_TEST_ID = `${SESSION_PREVIEW_TEST_ID}UpSell` as const;
-export const SESSION_PREVIEW_NO_DATA_TEST_ID = `${SESSION_PREVIEW_TEST_ID}NoData` as const;
/* Response section */
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx
index a111e5469e614b..4b0b5816f30146 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.test.tsx
@@ -134,9 +134,9 @@ describe('', () => {
loading: true,
});
- const { getAllByTestId } = render(renderThreatIntelligenceOverview(panelContextValue));
+ const { getByTestId } = render(renderThreatIntelligenceOverview(panelContextValue));
- expect(getAllByTestId(LOADING_TEST_ID)).toHaveLength(2);
+ expect(getByTestId(LOADING_TEST_ID)).toBeInTheDocument();
});
it('should navigate to left section Insights tab when clicking on button', () => {
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx
index 7fbcd048a1197e..9b5ad192ec3713 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/threat_intelligence_overview.tsx
@@ -67,6 +67,7 @@ export const ThreatIntelligenceOverview: FC = () => {
iconType: 'arrowStart',
}}
data-test-subj={INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}
+ content={{ loading }}
>
{
data-test-subj={`${INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}Container`}
>
{
data-test-subj={INSIGHTS_THREAT_INTELLIGENCE_TEST_ID}
/>
', () => {
expect(getByTestId(ENTITIES_USER_OVERVIEW_LAST_SEEN_TEST_ID)).toHaveTextContent('—');
});
+ it('should render loading if user details returns loading as true', () => {
+ mockUseUserDetails.mockReturnValue([true, { userDetails: null }]);
+ mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true });
+
+ const { getByTestId, queryByTestId } = render(
+
+
+
+
+
+ );
+ expect(getByTestId(ENTITIES_USER_OVERVIEW_LOADING_TEST_ID)).toBeInTheDocument();
+ expect(queryByTestId(ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID)).not.toBeInTheDocument();
+ });
+
+ it('should render loading if risk score returns loading as true', () => {
+ mockUseUserDetails.mockReturnValue([false, { userDetails: null }]);
+ mockUseRiskScore.mockReturnValue({ data: null, isAuthorized: true, loading: true });
+
+ const { getByTestId, queryByTestId } = render(
+
+
+
+
+
+ );
+ expect(getByTestId(ENTITIES_USER_OVERVIEW_LOADING_TEST_ID)).toBeInTheDocument();
+ expect(queryByTestId(ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID)).not.toBeInTheDocument();
+ });
+
it('should navigate to left panel entities tab when clicking on title', () => {
mockUseUserDetails.mockReturnValue([false, { userDetails: userData }]);
mockUseRiskScore.mockReturnValue({ data: riskLevel, isAuthorized: true });
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx
index 941a5c299ffbe6..e905607bfa740f 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/user_entity_overview.tsx
@@ -14,9 +14,11 @@ import {
useEuiTheme,
useEuiFontSize,
EuiIconTip,
+ EuiSkeletonText,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { getOr } from 'lodash/fp';
+import { i18n } from '@kbn/i18n';
import { useExpandableFlyoutContext } from '@kbn/expandable-flyout';
import { FormattedMessage } from '@kbn/i18n-react';
import { LeftPanelInsightsTab, LeftPanelKey } from '../../left';
@@ -36,7 +38,11 @@ import { RiskScore } from '../../../explore/components/risk_score/severity/commo
import { useSourcererDataView } from '../../../common/containers/sourcerer';
import { useGlobalTime } from '../../../common/containers/use_global_time';
import { useRiskScore } from '../../../explore/containers/risk_score';
-import * as i18n from '../../../overview/components/user_overview/translations';
+import {
+ USER_DOMAIN,
+ LAST_SEEN,
+ USER_RISK_CLASSIFICATION,
+} from '../../../overview/components/user_overview/translations';
import {
ENTITIES_USER_OVERVIEW_TEST_ID,
ENTITIES_USER_OVERVIEW_DOMAIN_TEST_ID,
@@ -44,6 +50,7 @@ import {
ENTITIES_USER_OVERVIEW_RISK_LEVEL_TEST_ID,
ENTITIES_USER_OVERVIEW_LINK_TEST_ID,
TECHNICAL_PREVIEW_ICON_TEST_ID,
+ ENTITIES_USER_OVERVIEW_LOADING_TEST_ID,
} from './test_ids';
import { useObservedUserDetails } from '../../../explore/users/containers/users/observed_details';
@@ -90,14 +97,18 @@ export const UserEntityOverview: React.FC = ({ userName
() => (userName ? buildUserNamesFilter([userName]) : undefined),
[userName]
);
- const [_, { userDetails }] = useObservedUserDetails({
+ const [isUserDetailsLoading, { userDetails }] = useObservedUserDetails({
endDate: to,
userName,
indexNames: selectedPatterns,
startDate: from,
});
- const { data: userRisk, isAuthorized } = useRiskScore({
+ const {
+ data: userRisk,
+ isAuthorized,
+ loading: isRiskScoreLoading,
+ } = useRiskScore({
filterQuery,
riskEntity: RiskScoreEntity.user,
timerange,
@@ -106,7 +117,7 @@ export const UserEntityOverview: React.FC = ({ userName
const userDomain: DescriptionList[] = useMemo(
() => [
{
- title: i18n.USER_DOMAIN,
+ title: USER_DOMAIN,
description: (
= ({ userName
const userLastSeen: DescriptionList[] = useMemo(
() => [
{
- title: i18n.LAST_SEEN,
+ title: LAST_SEEN,
description: (
= ({ userName
{
title: (
<>
- {i18n.USER_RISK_CLASSIFICATION}
+ {USER_RISK_CLASSIFICATION}
= ({ userName
-
-
-
-
-
- {isAuthorized ? (
-
- ) : (
+ {isUserDetailsLoading || isRiskScoreLoading ? (
+
+ ) : (
+
+
- )}
-
-
+
+
+ {isAuthorized ? (
+
+ ) : (
+
+ )}
+
+
+ )}
);
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx
index 3e273d7d126f33..208aa52cfeeca2 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/shared/components/expandable_panel.tsx
@@ -16,13 +16,13 @@ import {
EuiLink,
EuiTitle,
EuiText,
- EuiLoadingSpinner,
useEuiTheme,
EuiToolTip,
+ EuiSkeletonText,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import type { IconType } from '@elastic/eui';
import { css } from '@emotion/react';
-import { i18n } from '@kbn/i18n';
export interface ExpandablePanelPanelProps {
header: {
@@ -197,11 +197,13 @@ export const ExpandablePanel: React.FC = ({
}, [children, expandable, toggleStatus]);
const content = loading ? (
-
-
-
-
-
+
) : error ? null : (
children
);
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx
index e7889d30526c5c..d55e85b3e978b8 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.test.tsx
@@ -15,4 +15,10 @@ describe('', () => {
const { getByTestId } = render();
expect(getByTestId(FLYOUT_LOADING_TEST_ID)).toBeInTheDocument();
});
+
+ it('should render loading when data test subject is passed', () => {
+ const { getByTestId, queryByTestId } = render();
+ expect(getByTestId('test-id')).toBeInTheDocument();
+ expect(queryByTestId(FLYOUT_LOADING_TEST_ID)).not.toBeInTheDocument();
+ });
});
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx
index e1186c6257efde..03ecb298c3d181 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/shared/components/flyout_loading.tsx
@@ -10,18 +10,25 @@ import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
import { css } from '@emotion/react';
import { FLYOUT_LOADING_TEST_ID } from '../test_ids';
+interface FlyoutLoadingProps {
+ /**
+ Data test subject string for testing
+ */
+ ['data-test-subj']?: string;
+}
+
/**
* Use this when you need to show a loading state in the flyout
*/
-export const FlyoutLoading: React.VFC = () => (
+export const FlyoutLoading: React.FC = ({
+ 'data-test-subj': dataTestSubj = FLYOUT_LOADING_TEST_ID,
+}) => (
-
+
);
-
-FlyoutLoading.displayName = 'FlyoutLoading';
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts
index a669d4f5f7a7e4..4636b96beaa04b 100644
--- a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts
+++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/alert_details_left_panel_session_view_tab.cy.ts
@@ -5,10 +5,7 @@
* 2.0.
*/
-import {
- DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON,
- DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR,
-} from '../../../../screens/expandable_flyout/alert_details_left_panel_session_view_tab';
+import { DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON } from '../../../../screens/expandable_flyout/alert_details_left_panel_session_view_tab';
import {
DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB,
DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_BUTTON_GROUP,
@@ -48,12 +45,6 @@ describe.skip(
cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON)
.should('be.visible')
.and('have.text', 'Session View');
-
- // TODO ideally we would have a test for the session view component instead
- cy.get(DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR)
- .should('be.visible')
- .and('contain.text', 'Unable to display session view')
- .and('contain.text', 'There was an error displaying session view');
});
}
);
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts
index 9761e04aadc867..a2aad15ff504bc 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_response_tab.ts
@@ -6,10 +6,15 @@
*/
import { RESPONSE_TAB_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/test_ids';
-import { RESPONSE_NO_DATA_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids';
+import {
+ RESPONSE_DETAILS_TEST_ID,
+ RESPONSE_NO_DATA_TEST_ID,
+} from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids';
import { getDataTestSubjectSelector } from '../../helpers/common';
export const DOCUMENT_DETAILS_FLYOUT_RESPONSE_TAB =
getDataTestSubjectSelector(RESPONSE_TAB_TEST_ID);
+export const DOCUMENT_DETAILS_FLYOUT_RESPONSE_DETAILS =
+ getDataTestSubjectSelector(RESPONSE_DETAILS_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_RESPONSE_EMPTY =
getDataTestSubjectSelector(RESPONSE_NO_DATA_TEST_ID);
diff --git a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts
index d7e4f86d79c826..d50f645cf9359c 100644
--- a/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts
+++ b/x-pack/test/security_solution_cypress/cypress/screens/expandable_flyout/alert_details_left_panel_session_view_tab.ts
@@ -6,12 +6,8 @@
*/
import { VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/tabs/test_ids';
-import { SESSION_VIEW_ERROR_TEST_ID } from '@kbn/security-solution-plugin/public/flyout/left/components/test_ids';
import { getDataTestSubjectSelector } from '../../helpers/common';
export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON = getDataTestSubjectSelector(
VISUALIZE_TAB_SESSION_VIEW_BUTTON_TEST_ID
);
-export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_ERROR = getDataTestSubjectSelector(
- SESSION_VIEW_ERROR_TEST_ID
-);
From af51e263339664f66331d9c00d388c084b69abed Mon Sep 17 00:00:00 2001
From: Philippe Oberti
Date: Wed, 27 Sep 2023 22:26:36 +0200
Subject: [PATCH 08/22] [Security Solution] expandable flyout - fix prevalence
query not taking into account fields with multiple values (#166891)
---
.../components/prevalence_details.test.tsx | 39 ++++++++++++++--
.../left/components/prevalence_details.tsx | 46 ++++++++++---------
.../components/prevalence_overview.test.tsx | 25 ++++++++--
.../right/components/prevalence_overview.tsx | 2 +-
.../shared/hooks/use_prevalence.test.tsx | 2 +-
.../flyout/shared/hooks/use_prevalence.ts | 7 ++-
.../utils/highlighted_fields_helpers.test.ts | 6 +--
.../utils/highlighted_fields_helpers.ts | 2 +-
8 files changed, 87 insertions(+), 42 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx
index 9a1181fe006411..b8f09506ef46b5 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.test.tsx
@@ -79,7 +79,7 @@ describe('PrevalenceDetails', () => {
data: [
{
field: field1,
- value: 'value1',
+ values: ['value1'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.05,
@@ -87,7 +87,7 @@ describe('PrevalenceDetails', () => {
},
{
field: field2,
- value: 'value2',
+ values: ['value2'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.5,
@@ -124,7 +124,7 @@ describe('PrevalenceDetails', () => {
data: [
{
field: 'field1',
- value: 'value1',
+ values: ['value1'],
alertCount: 1000,
docCount: 2000000,
hostPrevalence: 0.05,
@@ -154,6 +154,35 @@ describe('PrevalenceDetails', () => {
);
});
+ it('should render multiple values in value column', () => {
+ (usePrevalence as jest.Mock).mockReturnValue({
+ loading: false,
+ error: false,
+ data: [
+ {
+ field: 'field1',
+ values: ['value1', 'value2'],
+ alertCount: 1000,
+ docCount: 2000000,
+ hostPrevalence: 0.05,
+ userPrevalence: 0.1,
+ },
+ ],
+ });
+
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+
+ expect(getByTestId(PREVALENCE_DETAILS_TABLE_TEST_ID)).toBeInTheDocument();
+ expect(getByTestId(PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID)).toHaveTextContent('value1');
+ expect(getByTestId(PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID)).toHaveTextContent('value2');
+ });
+
it('should render the table with only basic columns if license is not platinum', () => {
const field1 = 'field1';
const field2 = 'field2';
@@ -163,7 +192,7 @@ describe('PrevalenceDetails', () => {
data: [
{
field: field1,
- value: 'value1',
+ values: ['value1'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.05,
@@ -171,7 +200,7 @@ describe('PrevalenceDetails', () => {
},
{
field: field2,
- value: 'value2',
+ values: ['value2'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.5,
diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx
index 9ddae380c91362..418e895dda3a2c 100644
--- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx
@@ -74,7 +74,7 @@ const columns: Array> = [
width: '20%',
},
{
- field: 'value',
+ field: 'values',
name: (
> = [
/>
),
'data-test-subj': PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID,
- render: (value: string) => {value},
+ render: (values: string[]) => (
+
+ {values.map((value) => (
+
+ {value}
+
+ ))}
+
+ ),
width: '20%',
},
{
@@ -113,9 +121,9 @@ const columns: Array> = [
),
'data-test-subj': PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID,
render: (data: PrevalenceDetailsRow) => {
- const dataProviders = [
- getDataProvider(data.field, `timeline-indicator-${data.field}-${data.value}`, data.value),
- ];
+ const dataProviders = data.values.map((value) =>
+ getDataProvider(data.field, `timeline-indicator-${data.field}-${value}`, value)
+ );
return data.alertCount > 0 ? (
> = [
),
'data-test-subj': PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID,
render: (data: PrevalenceDetailsRow) => {
- const dataProviders = [
- {
- ...getDataProvider(
- data.field,
- `timeline-indicator-${data.field}-${data.value}`,
- data.value
+ const dataProviders = data.values.map((value) => ({
+ ...getDataProvider(data.field, `timeline-indicator-${data.field}-${value}`, value),
+ and: [
+ getDataProviderAnd(
+ 'event.kind',
+ `timeline-indicator-event.kind-not-signal`,
+ 'signal',
+ IS_OPERATOR,
+ true
),
- and: [
- getDataProviderAnd(
- 'event.kind',
- `timeline-indicator-event.kind-not-signal`,
- 'signal',
- IS_OPERATOR,
- true
- ),
- ],
- },
- ];
+ ],
+ }));
return data.docCount > 0 ? (
', () => {
it('should render only data with prevalence less than 10%', () => {
const field1 = 'field1';
const field2 = 'field2';
+ const field3 = 'field3';
(usePrevalence as jest.Mock).mockReturnValue({
loading: false,
error: false,
data: [
{
field: field1,
- value: 'value1',
+ values: ['value1'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.05,
@@ -105,7 +106,15 @@ describe('', () => {
},
{
field: field2,
- value: 'value2',
+ values: ['value2', 'value22'],
+ alertCount: 1,
+ docCount: 1,
+ hostPrevalence: 0.06,
+ userPrevalence: 0.2,
+ },
+ {
+ field: field3,
+ values: ['value3'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.5,
@@ -126,8 +135,14 @@ describe('', () => {
const iconDataTestSubj2 = `${PREVALENCE_TEST_ID}${field2}Icon`;
const valueDataTestSubj2 = `${PREVALENCE_TEST_ID}${field2}Value`;
- expect(queryByTestId(iconDataTestSubj2)).not.toBeInTheDocument();
- expect(queryByTestId(valueDataTestSubj2)).not.toBeInTheDocument();
+ expect(getByTestId(iconDataTestSubj2)).toBeInTheDocument();
+ expect(getByTestId(valueDataTestSubj2)).toBeInTheDocument();
+ expect(getByTestId(valueDataTestSubj2)).toHaveTextContent('field2, value2,value22 is uncommon');
+
+ const iconDataTestSubj3 = `${PREVALENCE_TEST_ID}${field3}Icon`;
+ const valueDataTestSubj3 = `${PREVALENCE_TEST_ID}${field3}Value`;
+ expect(queryByTestId(iconDataTestSubj3)).not.toBeInTheDocument();
+ expect(queryByTestId(valueDataTestSubj3)).not.toBeInTheDocument();
expect(queryByText(NO_DATA_MESSAGE)).not.toBeInTheDocument();
});
@@ -139,7 +154,7 @@ describe('', () => {
data: [
{
field: 'field1',
- value: 'value1',
+ values: ['value1'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.05,
diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx
index 233057a7245d94..5aad186b24c0c8 100644
--- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx
@@ -99,7 +99,7 @@ export const PrevalenceOverview: FC = () => {
}
data-test-subj={`${PREVALENCE_TEST_ID}${d.field}`}
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx
index 3f7d73923b8fa6..a9c12adfc84ca6 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx
+++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.test.tsx
@@ -124,7 +124,7 @@ describe('usePrevalence', () => {
expect(hookResult.result.current.data).toEqual([
{
field: 'host.name',
- value: 'host-1',
+ values: ['host-1'],
alertCount: 1,
docCount: 1,
hostPrevalence: 0.1,
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts
index a78295139d7244..8811c91a0b34d2 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts
+++ b/x-pack/plugins/security_solution/public/flyout/shared/hooks/use_prevalence.ts
@@ -6,7 +6,6 @@
*/
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
-import { isArray } from 'lodash/fp';
import { useMemo } from 'react';
import { useHighlightedFields } from './use_highlighted_fields';
import { convertHighlightedFieldsToPrevalenceFilters } from '../utils/highlighted_fields_helpers';
@@ -24,7 +23,7 @@ import { EventKind } from '../constants/event_kinds';
export interface PrevalenceData {
field: string;
- value: string;
+ values: string[];
alertCount: number;
docCount: number;
hostPrevalence: number;
@@ -91,7 +90,7 @@ export const usePrevalence = ({
const fieldNames = Object.keys(data.aggregations[FIELD_NAMES_AGG_KEY].buckets);
fieldNames.forEach((fieldName: string) => {
- const fieldValue = highlightedFields[fieldName].values;
+ const fieldValues = highlightedFields[fieldName].values;
// retrieves the number of signals for the current field/value pair
const alertCount =
@@ -131,7 +130,7 @@ export const usePrevalence = ({
items.push({
field: fieldName,
- value: isArray(fieldValue) ? fieldValue[0] : fieldValue,
+ values: fieldValues,
alertCount,
docCount,
hostPrevalence,
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.test.ts b/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.test.ts
index ec92745455e213..3992966878192b 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.test.ts
@@ -58,12 +58,12 @@ describe('convertHighlightedFieldsToPrevalenceFilters', () => {
values: ['host-1'],
},
'user.name': {
- values: ['user-1'],
+ values: ['user-1', 'user-2'],
},
};
expect(convertHighlightedFieldsToPrevalenceFilters(highlightedFields)).toEqual({
- 'host.name': { match: { 'host.name': 'host-1' } },
- 'user.name': { match: { 'user.name': 'user-1' } },
+ 'host.name': { terms: { 'host.name': ['host-1'] } },
+ 'user.name': { terms: { 'user.name': ['user-1', 'user-2'] } },
});
});
});
diff --git a/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.ts b/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.ts
index 094187b570251b..d41ff1b75f28a5 100644
--- a/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.ts
+++ b/x-pack/plugins/security_solution/public/flyout/shared/utils/highlighted_fields_helpers.ts
@@ -48,7 +48,7 @@ export const convertHighlightedFieldsToPrevalenceFilters = (
return {
...acc,
- [curr]: { match: { [curr]: Array.isArray(values) ? values[0] : values } },
+ [curr]: { terms: { [curr]: values } },
};
}, []) as unknown as Record;
};
From 9fba1e3f243c3ba3f802011a5c89a5a930268cb8 Mon Sep 17 00:00:00 2001
From: Jonathan Budzenski
Date: Wed, 27 Sep 2023 15:36:44 -0500
Subject: [PATCH 09/22] fix type import
---
.../public/application/lib/snapshot_list_params.ts | 2 +-
.../home/snapshot_list/components/snapshot_search_bar.tsx | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts b/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
index 20276ae58b8e41..352eb658dd0238 100644
--- a/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
+++ b/x-pack/plugins/snapshot_restore/public/application/lib/snapshot_list_params.ts
@@ -6,7 +6,7 @@
*/
import { Direction, Query } from '@elastic/eui';
-import { SchemaType } from '@elastic/eui/src/components/search_bar/search_box';
+import { SchemaType } from '@elastic/eui/src/components/search_bar/search_bar';
export type SortField =
| 'snapshot'
diff --git a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
index 4b5c5af331832f..c2972a4c0a4d12 100644
--- a/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
+++ b/x-pack/plugins/snapshot_restore/public/application/sections/home/snapshot_list/components/snapshot_search_bar.tsx
@@ -11,7 +11,7 @@ import useDebounce from 'react-use/lib/useDebounce';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { SearchFilterConfig } from '@elastic/eui/src/components/search_bar/search_filters';
-import { SchemaType } from '@elastic/eui/src/components/search_bar/search_box';
+import { SchemaType } from '@elastic/eui/src/components/search_bar/search_bar';
import { EuiSearchBarOnChangeArgs } from '@elastic/eui/src/components/search_bar/search_bar';
import { EuiButton, EuiCallOut, EuiSearchBar, EuiSpacer, Query } from '@elastic/eui';
import { SnapshotDeleteProvider } from '../../../../components';
From 0c680d7783858172dfbabde6e0f18143c18a97a0 Mon Sep 17 00:00:00 2001
From: Tim Sullivan
Date: Wed, 27 Sep 2023 14:22:46 -0700
Subject: [PATCH 10/22] Project Side Navigation: Use EuiCollapsibleNavBeta
component (#164910)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
Closes https://github.com/elastic/kibana/issues/162507
Relates to https://github.com/elastic/kibana/issues/166545
^ additional IA-related tasks - related to the alignment discussions -
can be found here
## Work for next steps
In this PR, some work items are being saved for a next PR:
1. _Only affects Search solution_: Navigation "group titles" do not
create a breadcrumb item, as sub-items in the group are not
hierarchically under the title. To address this, group titles may be
going away from the design.
https://github.com/elastic/kibana/issues/167323
2. _Only affects Observability solution_: Navigation accordions can not
be collapsed and do not show arrow icons. To address this, in a later PR
we will add internal state management for the open/closed state of each
accordion. https://github.com/elastic/kibana/issues/167328
3. _Affects all solutions:_ The "collapsed" state of the side nav should
show a docked view with icons-only. To address this, in later PRs we
will bring Security solution into the unified nav components.
4. https://github.com/elastic/kibana/issues/167326
5. https://github.com/elastic/kibana/issues/167330
6. https://github.com/elastic/kibana/issues/167332
### Recordings
These videos show a before-and-after with the new UI.
| project | old | new |
|--|--|--|
|observability|
https://github.com/elastic/kibana/assets/908371/663765a3-4e4b-416e-b7d5-7d87eece83e8
|
|
|search|
https://github.com/elastic/kibana/assets/908371/f383773e-27a8-4485-8289-274d8231b960
|
|
|security|
https://github.com/elastic/kibana/assets/908371/481f4533-64e5-41db-bc8e-5012f82c188a
| *will change to the new style after this PR and the flyout/panel
support are completed |
### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---------
Co-authored-by: Sébastien Loix
---
.../src/ui/project/app_menu.tsx | 1 -
.../src/ui/project/header.test.tsx | 31 +-
.../src/ui/project/header.tsx | 50 +--
.../src/ui/project/navigation.tsx | 60 +---
.../src/project_navigation.ts | 15 +-
.../analytics/default_navigation.ts | 19 +-
.../devtools/default_navigation.ts | 25 +-
.../management/default_navigation.ts | 8 +-
packages/default-nav/ml/default_navigation.ts | 14 +-
.../chrome/navigation/mocks/src/storybook.ts | 4 +-
.../shared-ux/chrome/navigation/src/styles.ts | 15 -
.../default_navigation.test.tsx.snap | 289 ++++++----------
.../src/ui/components/group_as_link.tsx | 61 ----
.../src/ui/components/navigation.test.tsx | 205 ++++-------
.../src/ui/components/navigation_group.tsx | 2 +-
.../src/ui/components/navigation_item.tsx | 21 +-
.../ui/components/navigation_section_ui.tsx | 116 +++----
.../src/ui/components/navigation_ui.tsx | 19 +-
.../src/ui/components/recently_accessed.tsx | 54 ++-
.../src/ui/default_navigation.test.tsx | 42 +--
.../navigation/src/ui/default_navigation.tsx | 29 +-
.../src/ui/hooks/use_init_navnode.ts | 16 +-
.../navigation/src/ui/navigation.stories.tsx | 325 +++++++++++-------
.../chrome/navigation/src/ui/types.ts | 27 +-
.../components/side_navigation/index.tsx | 236 +++++++------
.../serverless_search/public/layout/nav.tsx | 150 ++++----
.../page_objects/svl_common_navigation.ts | 24 +-
.../test_suites/search/navigation.ts | 17 +-
28 files changed, 763 insertions(+), 1112 deletions(-)
delete mode 100644 packages/shared-ux/chrome/navigation/src/styles.ts
delete mode 100644 packages/shared-ux/chrome/navigation/src/ui/components/group_as_link.tsx
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx
index 0fb7a3f1bd94c8..22ff7c9415ba8e 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/app_menu.tsx
@@ -13,7 +13,6 @@ import React from 'react';
import { HeaderActionMenu } from '../header/header_action_menu';
interface AppMenuBarProps {
- isOpen: boolean;
headerActionMenuMounter: { mount: MountPoint | undefined };
}
export const AppMenuBar = ({ headerActionMenuMounter }: AppMenuBarProps) => {
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx
index 167b11629ce553..d4886d8180c476 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.test.tsx
@@ -9,7 +9,7 @@
import { EuiHeader } from '@elastic/eui';
import { applicationServiceMock } from '@kbn/core-application-browser-mocks';
import { docLinksServiceMock } from '@kbn/core-doc-links-browser-mocks';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
import React from 'react';
import * as Rx from 'rxjs';
import { ProjectHeader, Props as ProjectHeaderProps } from './header';
@@ -45,35 +45,10 @@ describe('Header', () => {
);
- expect(await screen.findByTestId('toggleNavButton')).toBeVisible();
+ expect(await screen.findByTestId('euiCollapsibleNavButton')).toBeVisible();
expect(await screen.findByText('Hello, world!')).toBeVisible();
});
- it('can collapse and uncollapse', async () => {
- render(
-
- Hello, goodbye!
-
- );
-
- expect(await screen.findByTestId('toggleNavButton')).toBeVisible();
- expect(await screen.findByText('Hello, goodbye!')).toBeVisible(); // title is shown
-
- const toggleNav = async () => {
- fireEvent.click(await screen.findByTestId('toggleNavButton')); // click
-
- expect(await screen.findByText('Hello, goodbye!')).not.toBeVisible();
-
- fireEvent.click(await screen.findByTestId('toggleNavButton')); // click again
-
- expect(await screen.findByText('Hello, goodbye!')).toBeVisible(); // title is shown
- };
-
- await toggleNav();
- await toggleNav();
- await toggleNav();
- });
-
it('displays the link to projects', async () => {
render(
@@ -81,7 +56,7 @@ describe('Header', () => {
);
- const projectsLink = await screen.getByTestId('projectsLink');
+ const projectsLink = screen.getByTestId('projectsLink');
expect(projectsLink).toHaveAttribute('href', '/projects/');
expect(projectsLink).toHaveTextContent('My Project');
});
diff --git a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx
index 1cbf8eaa9af0a6..8767c1f9c53f76 100644
--- a/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx
+++ b/packages/core/chrome/core-chrome-browser-internal/src/ui/project/header.tsx
@@ -12,10 +12,7 @@ import {
EuiHeaderLogo,
EuiHeaderSection,
EuiHeaderSectionItem,
- EuiHeaderSectionItemButton,
- EuiIcon,
EuiLoadingSpinner,
- htmlIdGenerator,
useEuiTheme,
EuiThemeComputed,
} from '@elastic/eui';
@@ -35,8 +32,7 @@ import { MountPoint } from '@kbn/core-mount-utils-browser';
import { i18n } from '@kbn/i18n';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import { Router } from '@kbn/shared-ux-router';
-import React, { createRef, useCallback, useState } from 'react';
-import useLocalStorage from 'react-use/lib/useLocalStorage';
+import React, { useCallback } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { debounceTime, Observable, of } from 'rxjs';
import { useHeaderActionMenuMounter } from '../header/header_action_menu';
@@ -66,13 +62,6 @@ const getHeaderCss = ({ size }: EuiThemeComputed) => ({
top: 2px;
`,
},
- nav: {
- toggleNavButton: css`
- border-right: 1px solid #d3dae6;
- margin-left: -1px;
- padding-right: ${size.xs};
- `,
- },
projectName: {
link: css`
/* TODO: make header layout more flexible? */
@@ -123,7 +112,6 @@ export interface Props {
prependBasePath: (url: string) => string;
}
-const LOCAL_STORAGE_IS_OPEN_KEY = 'PROJECT_NAVIGATION_OPEN' as const;
const LOADING_DEBOUNCE_TIME = 80;
type LogoProps = Pick & {
@@ -186,9 +174,6 @@ export const ProjectHeader = ({
docLinks,
...observables
}: Props) => {
- const [navId] = useState(htmlIdGenerator()());
- const [isOpen, setIsOpen] = useLocalStorage(LOCAL_STORAGE_IS_OPEN_KEY, true);
- const toggleCollapsibleNavRef = createRef void }>();
const headerActionMenuMounter = useHeaderActionMenuMounter(observables.actionMenu$);
const projectsUrl = useObservable(observables.projectsUrl$);
const projectName = useObservable(observables.projectName$);
@@ -210,34 +195,9 @@ export const ProjectHeader = ({