[Security Solution] Add callout to promote blog post (#195943)

**Resolves: #195423**

## Summary

This PR adds a callout to the Rule Management page. This callout
displays a
[link](https://www.elastic.co/blog/elastic-security-detection-engineering)
to a post in Elastic blog.

Once a user clicks on "x" in the top-right corner the callout will be
dismissed forever. Dismissal state is saved in `localStorage`.

This is only for ESS v8.16.0 and beyond. Not for Serverless.

⚠️ Currently the
[link](https://www.elastic.co/blog/elastic-security-detection-engineering)
leads to a 404 page because the blog post is not yet created. It'll be
published in time for 8.16 release.

⚠️ UI copy is not final. It'll be reviewed by the Docs folks on Monday.
I'll change it to their suggestion once they review it on Monday.

### Screenshot

<img width="1392" alt="Scherm­afbeelding 2024-10-11 om 16 43 59"
src="https://github.com/user-attachments/assets/282430c1-4b02-4188-a052-5027e7433981">

---------

Co-authored-by: Joe Peeples <joe.peeples@elastic.co>
This commit is contained in:
Nikita Indik 2024-10-14 23:10:21 +02:00 committed by GitHub
parent 06a2faa6a9
commit ee7bc7ed83
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 103 additions and 0 deletions

View file

@ -22,6 +22,7 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout';
import { MlJobCompatibilityCallout } from '../../../../detections/components/callouts/ml_job_compatibility_callout';
import { NeedAdminForUpdateRulesCallOut } from '../../../../detections/components/callouts/need_admin_for_update_callout';
import { BlogPostDetectionEngineeringCallout } from '../../../../detections/components/callouts/blog_post_detection_engineering_callout';
import { AddElasticRulesButton } from '../../../../detections/components/rules/pre_packaged_rules/add_elastic_rules_button';
import { ValueListsFlyout } from '../../../../detections/components/value_lists_management_flyout';
import { useUserData } from '../../../../detections/components/user_info';
@ -173,6 +174,7 @@ const RulesPageComponent: React.FC = () => {
kibanaServices={kibanaServices}
categories={[DEFAULT_APP_CATEGORIES.security.id]}
/>
<BlogPostDetectionEngineeringCallout />
<RuleFeatureTour />
<AllRules data-test-subj="all-rules" />
</SecuritySolutionPageWrapper>

View file

@ -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, { useCallback } from 'react';
import { css } from '@emotion/css';
import avcBannerBackground from '@kbn/avc-banner/src/avc_banner_background.svg';
import { EuiSpacer, EuiButton, EuiCallOut, useEuiTheme } from '@elastic/eui';
import { type CallOutMessage } from '../../../../common/components/callouts';
import { useCallOutStorage } from '../../../../common/components/callouts/use_callout_storage';
import * as i18n from './translations';
const BLOG_POST_URL = 'https://www.elastic.co/blog/elastic-security-detection-engineering';
const calloutMessage: CallOutMessage = {
type: 'success',
id: 'blog-post-elastic-security-detection-engineering',
title: i18n.NEW_FEATURES_BLOG_POST_CALLOUT_TITLE,
description: <Description />,
};
export function BlogPostDetectionEngineeringCallout() {
const { euiTheme } = useEuiTheme();
const { isVisible, dismiss } = useCallOutStorage([calloutMessage], 'detections');
const calloutStyles = css({
paddingLeft: `${euiTheme.size.xl}`,
backgroundImage: `url(${avcBannerBackground})`,
backgroundRepeat: 'no-repeat',
backgroundPositionX: 'right',
backgroundPositionY: 'bottom',
});
const handleDismiss = useCallback(() => {
dismiss(calloutMessage);
}, [dismiss]);
if (!isVisible(calloutMessage)) {
return null;
}
return (
<>
<EuiCallOut
title={calloutMessage.title}
color={calloutMessage.type}
iconType="cheer"
onDismiss={handleDismiss}
className={calloutStyles}
>
{calloutMessage.description}
</EuiCallOut>
<EuiSpacer size="l" />
</>
);
}
function Description() {
return (
<>
{i18n.NEW_FEATURES_BLOG_POST_CALLOUT_DESCRIPTION}
<EuiSpacer size="s" />
<EuiButton size="s" color="success" href={BLOG_POST_URL} target="_blank">
{i18n.NEW_FEATURES_BLOG_POST_CALLOUT_BUTTON_LABEL}
</EuiButton>
</>
);
}

View file

@ -0,0 +1,29 @@
/*
* 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';
export const NEW_FEATURES_BLOG_POST_CALLOUT_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.newFeaturesBlogPostCallout.calloutTitle',
{
defaultMessage: `Discover the power of Elastic's threat detection!`,
}
);
export const NEW_FEATURES_BLOG_POST_CALLOUT_DESCRIPTION = i18n.translate(
'xpack.securitySolution.detectionEngine.newFeaturesBlogPostCallout.calloutDescription',
{
defaultMessage: 'Learn about new and existing detection capabilities of Elastic Security.',
}
);
export const NEW_FEATURES_BLOG_POST_CALLOUT_BUTTON_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.newFeaturesBlogPostCallout.calloutButtonLabel',
{
defaultMessage: 'Read the blog',
}
);