Skip to content

Commit

Permalink
[Security Solution][Bidirectional Integrations Banner][Crowdstrike][S…
Browse files Browse the repository at this point in the history
…entinelOne] Banner for bidirectional integrations (elastic#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">



![bid](https://github.com/user-attachments/assets/7f3730f8-7eed-4ca0-a67d-7658fe98d308)
  • Loading branch information
parkiino authored Nov 19, 2024
1 parent 467d737 commit 3c32748
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/kbn-doc-links/src/get_doc_links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
1 change: 1 addition & 0 deletions packages/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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}>
Expand All @@ -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" />
</>
)}
Expand Down

0 comments on commit 3c32748

Please sign in to comment.