mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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 <img width="1685" alt="image" src="https://github.com/user-attachments/assets/f360c391-6046-49a8-b9d4-56a598dc2b99"> <img width="1132" alt="image" src="https://github.com/user-attachments/assets/9a15dc52-172a-4ee9-8e39-831a524e5d0b"> DARK MODE <img width="1127" alt="image" src="https://github.com/user-attachments/assets/9ab39df4-960b-4a56-b9bf-8c2077304039"> 
This commit is contained in:
parent
467d7370ad
commit
3c3274829b
6 changed files with 156 additions and 3 deletions
|
@ -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`,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(<BidirectionalIntegrationsBanner {...formProps} />);
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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<BidirectionalIntegrationsBannerProps>(
|
||||
({ onDismiss }) => {
|
||||
const { docLinks } = useKibana().services;
|
||||
|
||||
const bannerTitle = (
|
||||
<EuiTextColor color="accent">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.bidirectionalIntegrationsBanner.title"
|
||||
defaultMessage={'NEW: Response enabled integration'}
|
||||
/>
|
||||
</EuiTextColor>
|
||||
);
|
||||
|
||||
return (
|
||||
<AccentCallout
|
||||
title={bannerTitle}
|
||||
iconType="cheer"
|
||||
onDismiss={onDismiss}
|
||||
data-test-subj={'bidirectionalIntegrationsCallout'}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.bidirectionalIntegrationsBanner.body"
|
||||
defaultMessage="Orchestrate response actions across endpoint vendors with bidirectional integrations. {learnmore}."
|
||||
values={{
|
||||
learnmore: (
|
||||
<EuiLink
|
||||
href={docLinks?.links.securitySolution.bidirectionalIntegrations}
|
||||
target="_blank"
|
||||
data-test-subj="bidirectionalIntegrationDocLink"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.bidirectionalIntegrations.doc.link"
|
||||
defaultMessage="Learn more"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</AccentCallout>
|
||||
);
|
||||
}
|
||||
);
|
||||
BidirectionalIntegrationsBanner.displayName = 'BidirectionalIntegrationsBanner';
|
|
@ -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';
|
||||
|
|
|
@ -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<Props> = 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<string | undefined>(undefined);
|
||||
const [selectedItemId, setSelectedItem] = useState<string | undefined>(undefined);
|
||||
const [isSideNavOpenOnMobile, setIsSideNavOpenOnMobile] = useState(false);
|
||||
|
@ -296,11 +301,27 @@ export const OverviewPage: React.FC<Props> = 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 (
|
||||
<EuiFlexGroup alignItems="flexStart" data-test-subj="epm.OverviewPage">
|
||||
<SideBar grow={2}>
|
||||
|
@ -317,7 +338,19 @@ export const OverviewPage: React.FC<Props> = memo(
|
|||
{isUnverified && <UnverifiedCallout />}
|
||||
{useIsStillYear2024() && isElasticDefend && showAVCBanner && (
|
||||
<>
|
||||
<AVCResultsBanner2024 onDismiss={onBannerDismiss} />
|
||||
<AVCResultsBanner2024 onDismiss={onAVCBannerDismiss} />
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
{isCrowdStrike && showCSResponseSupportBanner && (
|
||||
<>
|
||||
<BidirectionalIntegrationsBanner onDismiss={onCSResponseSupportBannerDismiss} />
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
{isSentinelOne && showSOReponseSupportBanner && (
|
||||
<>
|
||||
<BidirectionalIntegrationsBanner onDismiss={onSOResponseSupportBannerDismiss} />
|
||||
<EuiSpacer size="s" />
|
||||
</>
|
||||
)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue