From 3c3274829bdcb950ab4aff9abf1d106482de6267 Mon Sep 17 00:00:00 2001
From: Candace Park <56409205+parkiino@users.noreply.github.com>
Date: Mon, 18 Nov 2024 23:46:25 -0500
Subject: [PATCH] [Security Solution][Bidirectional Integrations
Banner][Crowdstrike][SentinelOne] Banner for bidirectional integrations
(#200625)
## Summary
- [x] Callouts for bidirectional integrations capabilities on Sentinel
One and Crowdstrike integrations.
- [x] Unit tests
# Screenshots
DARK MODE
![bid](https://github.com/user-attachments/assets/7f3730f8-7eed-4ca0-a67d-7658fe98d308)
---
packages/kbn-doc-links/src/get_doc_links.ts | 1 +
packages/kbn-doc-links/src/types.ts | 1 +
...idirectional_integrations_callout.test.tsx | 50 ++++++++++++++
.../bidirectional_integrations_callout.tsx | 66 +++++++++++++++++++
.../epm/screens/detail/components/index.tsx | 2 +
.../epm/screens/detail/overview/overview.tsx | 39 ++++++++++-
6 files changed, 156 insertions(+), 3 deletions(-)
create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.test.tsx
create mode 100644 x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.tsx
diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts
index 251d08dde715a..52a3e7b7427e7 100644
--- a/packages/kbn-doc-links/src/get_doc_links.ts
+++ b/packages/kbn-doc-links/src/get_doc_links.ts
@@ -470,6 +470,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
securitySolution: {
artifactControl: `${SECURITY_SOLUTION_DOCS}artifact-control.html`,
avcResults: `${ELASTIC_WEBSITE_URL}blog/elastic-av-comparatives-business-security-test`,
+ bidirectionalIntegrations: `${SECURITY_SOLUTION_DOCS}third-party-actions.html`,
trustedApps: `${SECURITY_SOLUTION_DOCS}trusted-apps-ov.html`,
eventFilters: `${SECURITY_SOLUTION_DOCS}event-filters.html`,
blocklist: `${SECURITY_SOLUTION_DOCS}blocklist.html`,
diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts
index f1a6a8d4b578d..d3ece5b61daa1 100644
--- a/packages/kbn-doc-links/src/types.ts
+++ b/packages/kbn-doc-links/src/types.ts
@@ -340,6 +340,7 @@ export interface DocLinks {
readonly aiAssistant: string;
readonly artifactControl: string;
readonly avcResults: string;
+ readonly bidirectionalIntegrations: string;
readonly trustedApps: string;
readonly eventFilters: string;
readonly eventMerging: string;
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.test.tsx
new file mode 100644
index 0000000000000..6dd9ff0e5f9b3
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.test.tsx
@@ -0,0 +1,50 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React from 'react';
+import { type RenderResult } from '@testing-library/react';
+
+import { createFleetTestRendererMock } from '../../../../../../../mock';
+
+import {
+ BidirectionalIntegrationsBanner,
+ type BidirectionalIntegrationsBannerProps,
+} from './bidirectional_integrations_callout';
+
+jest.mock('react-use/lib/useLocalStorage');
+
+describe('BidirectionalIntegrationsBanner', () => {
+ let formProps: BidirectionalIntegrationsBannerProps;
+ let renderResult: RenderResult;
+
+ beforeEach(() => {
+ formProps = {
+ onDismiss: jest.fn(),
+ };
+
+ const renderer = createFleetTestRendererMock();
+
+ renderResult = renderer.render();
+ });
+
+ it('should render bidirectional integrations banner', () => {
+ expect(renderResult.getByTestId('bidirectionalIntegrationsCallout')).toBeInTheDocument();
+ });
+
+ it('should contain a link to documentation', () => {
+ const docLink = renderResult.getByTestId('bidirectionalIntegrationDocLink');
+
+ expect(docLink).toBeInTheDocument();
+ expect(docLink.getAttribute('href')).toContain('third-party-actions.html');
+ });
+
+ it('should call `onDismiss` callback when user clicks dismiss', () => {
+ renderResult.getByTestId('euiDismissCalloutButton').click();
+
+ expect(formProps.onDismiss).toBeCalled();
+ });
+});
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.tsx
new file mode 100644
index 0000000000000..5f8375d2e7baa
--- /dev/null
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/bidirectional_integrations_callout.tsx
@@ -0,0 +1,66 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import React, { memo } from 'react';
+import styled from 'styled-components';
+import { EuiCallOut, EuiLink, EuiTextColor } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { useKibana } from '@kbn/kibana-react-plugin/public';
+
+const AccentCallout = styled(EuiCallOut)`
+ .euiCallOutHeader__title {
+ color: ${(props) => props.theme.eui.euiColorAccent};
+ }
+ background-color: ${(props) => props.theme.eui.euiPanelBackgroundColorModifiers.accent};
+`;
+
+export interface BidirectionalIntegrationsBannerProps {
+ onDismiss: () => void;
+}
+export const BidirectionalIntegrationsBanner = memo(
+ ({ onDismiss }) => {
+ const { docLinks } = useKibana().services;
+
+ const bannerTitle = (
+
+
+
+ );
+
+ return (
+
+
+
+
+ ),
+ }}
+ />
+
+ );
+ }
+);
+BidirectionalIntegrationsBanner.displayName = 'BidirectionalIntegrationsBanner';
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx
index 4aa1a543897c9..cb1fc1b396e42 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/components/index.tsx
@@ -6,7 +6,9 @@
*/
export { BackLink } from './back_link';
export { AddIntegrationButton } from './add_integration_button';
+export { CloudPostureThirdPartySupportCallout } from './cloud_posture_third_party_support_callout';
export { UpdateIcon } from './update_icon';
export { IntegrationAgentPolicyCount } from './integration_agent_policy_count';
export { IconPanel, LoadingIconPanel } from './icon_panel';
export { KeepPoliciesUpToDateSwitch } from './keep_policies_up_to_date_switch';
+export { BidirectionalIntegrationsBanner } from './bidirectional_integrations_callout';
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx
index e96e74b1bb96c..83cde5745071a 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/overview/overview.tsx
@@ -42,7 +42,10 @@ import { SideBarColumn } from '../../../components/side_bar_column';
import type { FleetStartServices } from '../../../../../../../plugin';
-import { CloudPostureThirdPartySupportCallout } from '../components/cloud_posture_third_party_support_callout';
+import {
+ CloudPostureThirdPartySupportCallout,
+ BidirectionalIntegrationsBanner,
+} from '../components';
import { Screenshots } from './screenshots';
import { Readme } from './readme';
@@ -172,6 +175,8 @@ export const OverviewPage: React.FC = memo(
const isUnverified = isPackageUnverified(packageInfo, packageVerificationKeyId);
const isPrerelease = isPackagePrerelease(packageInfo.version);
const isElasticDefend = packageInfo.name === 'endpoint';
+ const isSentinelOne = packageInfo.name === 'sentinel_one';
+ const isCrowdStrike = packageInfo.name === 'crowdstrike';
const [markdown, setMarkdown] = useState(undefined);
const [selectedItemId, setSelectedItem] = useState(undefined);
const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false);
@@ -296,11 +301,27 @@ export const OverviewPage: React.FC = memo(
const [showAVCBanner, setShowAVCBanner] = useState(
storage.get('securitySolution.showAvcBanner') ?? true
);
- const onBannerDismiss = useCallback(() => {
+ const [showCSResponseSupportBanner, setShowCSResponseSupportBanner] = useState(
+ storage.get('fleet.showCSResponseSupportBanner') ?? true
+ );
+ const [showSOReponseSupportBanner, setShowSOResponseSupportBanner] = useState(
+ storage.get('fleet.showSOReponseSupportBanner') ?? true
+ );
+ const onAVCBannerDismiss = useCallback(() => {
setShowAVCBanner(false);
storage.set('securitySolution.showAvcBanner', false);
}, [storage]);
+ const onCSResponseSupportBannerDismiss = useCallback(() => {
+ setShowCSResponseSupportBanner(false);
+ storage.set('fleet.showCSResponseSupportBanner', false);
+ }, [storage]);
+
+ const onSOResponseSupportBannerDismiss = useCallback(() => {
+ setShowSOResponseSupportBanner(false);
+ storage.set('fleet.showSOReponseSupportBanner', false);
+ }, [storage]);
+
return (
@@ -317,7 +338,19 @@ export const OverviewPage: React.FC = memo(
{isUnverified && }
{useIsStillYear2024() && isElasticDefend && showAVCBanner && (
<>
-
+
+
+ >
+ )}
+ {isCrowdStrike && showCSResponseSupportBanner && (
+ <>
+
+
+ >
+ )}
+ {isSentinelOne && showSOReponseSupportBanner && (
+ <>
+
>
)}