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 && ( + <> + )}